@tiangong-ai/cli 0.0.20 → 0.0.21

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 (45) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +148 -11
  3. package/dist/cli.js +1 -1
  4. package/dist/research/orchestration.js +166 -11
  5. package/dist/research/orchestration.js.map +1 -1
  6. package/dist/research/workspace/broker.js +407 -31
  7. package/dist/research/workspace/broker.js.map +1 -1
  8. package/dist/research/workspace/capabilities.js +65 -0
  9. package/dist/research/workspace/capabilities.js.map +1 -1
  10. package/dist/research/workspace/constants.d.ts +1 -1
  11. package/dist/research/workspace/constants.js +4 -0
  12. package/dist/research/workspace/constants.js.map +1 -1
  13. package/dist/research/workspace/evidence.d.ts +32 -0
  14. package/dist/research/workspace/evidence.js +235 -0
  15. package/dist/research/workspace/evidence.js.map +1 -0
  16. package/dist/research/workspace/executor.d.ts +11 -1
  17. package/dist/research/workspace/executor.js +678 -105
  18. package/dist/research/workspace/executor.js.map +1 -1
  19. package/dist/research/workspace/input-plan.d.ts +5 -0
  20. package/dist/research/workspace/input-plan.js +319 -0
  21. package/dist/research/workspace/input-plan.js.map +1 -0
  22. package/dist/research/workspace/journal.js +2 -1
  23. package/dist/research/workspace/journal.js.map +1 -1
  24. package/dist/research/workspace/preflight.d.ts +108 -0
  25. package/dist/research/workspace/preflight.js +261 -0
  26. package/dist/research/workspace/preflight.js.map +1 -0
  27. package/dist/research/workspace/projects.d.ts +5 -2
  28. package/dist/research/workspace/projects.js +290 -6
  29. package/dist/research/workspace/projects.js.map +1 -1
  30. package/dist/research/workspace/runtime.d.ts +5 -1
  31. package/dist/research/workspace/runtime.js +1314 -196
  32. package/dist/research/workspace/runtime.js.map +1 -1
  33. package/dist/research/workspace/sanitization.d.ts +5 -0
  34. package/dist/research/workspace/sanitization.js +72 -0
  35. package/dist/research/workspace/sanitization.js.map +1 -0
  36. package/dist/research/workspace/schemas.d.ts +17 -0
  37. package/dist/research/workspace/schemas.js +342 -0
  38. package/dist/research/workspace/schemas.js.map +1 -0
  39. package/dist/research/workspace/storage.js +4 -0
  40. package/dist/research/workspace/storage.js.map +1 -1
  41. package/dist/research/workspace/types.d.ts +160 -0
  42. package/dist/research/workspace/workspace.d.ts +14 -3
  43. package/dist/research/workspace/workspace.js +374 -12
  44. package/dist/research/workspace/workspace.js.map +1 -1
  45. package/package.json +2 -1
@@ -0,0 +1,261 @@
1
+ import { loadCapabilityDeclarations, verifyCapabilities } from "./capabilities.js";
2
+ import { canonicalJson, sha256Text } from "./storage.js";
3
+ import { loadWorkspaceConfig, verifyDoctorAttestation } from "./workspace.js";
4
+ export const RESEARCH_AGENT_PROTOCOL_OVERHEAD_TOKENS = {
5
+ codex: 5_000,
6
+ claude: 12_000,
7
+ };
8
+ export const RESEARCH_ESTIMATED_BYTES_PER_TOKEN = 3;
9
+ export const RESEARCH_MAX_REPAIR_SOURCE_BYTES = 32_000;
10
+ export const RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS = 2;
11
+ export const RESEARCH_BROKER_MAX_TURNS = 6;
12
+ export const RESEARCH_REPAIR_MAX_TURNS = 1;
13
+ export async function evaluateProjectPreflight(root, question, requirements, inputPlan) {
14
+ const config = await loadWorkspaceConfig(root);
15
+ const capabilities = await loadCapabilityDeclarations(root);
16
+ const capabilityVerification = await verifyCapabilities(root);
17
+ const doctorAttestation = config.mode === "production-research" ? await verifyDoctorAttestation(root) : null;
18
+ const networkCapabilities = capabilities.capabilities
19
+ .filter((capability) => capability.permissions.includes("brokered-network"))
20
+ .map((capability) => ({
21
+ id: capability.id,
22
+ allowedHosts: capability.allowedHosts,
23
+ accept: capability.http?.accept ?? null,
24
+ maxResponseBytes: capability.http?.maxResponseBytes ?? null,
25
+ maxItems: capability.http?.maxItems ?? null,
26
+ coverage: capability.coverage,
27
+ }));
28
+ const packageTokens = config.budget.packageMaxTokens;
29
+ const tokenReservation = Object.values(packageTokens).reduce((sum, value) => sum + value, 0);
30
+ const wallReservation = Object.values(config.budget.packageMaxWallSeconds).reduce((sum, value) => sum + value, 0);
31
+ const estimatedMaxCostUsd = config.producer.pricing && config.reviewer.pricing
32
+ ? roundMoney(reservedAgentPackageCost(config.producer, packageTokens.discover, config) +
33
+ reservedAgentPackageCost(config.producer, packageTokens.analyze, config) +
34
+ reservedAgentPackageCost(config.producer, packageTokens.synthesize, config) +
35
+ reservedAgentPackageCost(config.reviewer, packageTokens.review, config))
36
+ : null;
37
+ const gaps = [];
38
+ if (config.mode === "production-research" && !requirements) {
39
+ gaps.push("explicit-evidence-requirements-missing");
40
+ }
41
+ if (config.mode === "production-research" && (!config.producer.model || !config.reviewer.model)) {
42
+ gaps.push("explicit-agent-models-missing");
43
+ }
44
+ if (config.mode === "production-research" &&
45
+ (!config.producer.effort || !config.reviewer.effort)) {
46
+ gaps.push("explicit-agent-effort-missing");
47
+ }
48
+ if (config.mode === "production-research" && !config.producer.verbosity) {
49
+ gaps.push("explicit-codex-verbosity-missing");
50
+ }
51
+ if (config.mode === "production-research" &&
52
+ (!config.producer.pricing || !config.reviewer.pricing)) {
53
+ gaps.push("explicit-agent-pricing-missing");
54
+ }
55
+ if (config.producer.agent === config.reviewer.agent) {
56
+ gaps.push("independent-review-route-missing");
57
+ }
58
+ if (!networkCapabilities.length && !inputPlan)
59
+ gaps.push("no-evidence-acquisition-plan");
60
+ if (capabilityVerification.status !== "verified") {
61
+ gaps.push("capability-lock-missing-or-drifted");
62
+ }
63
+ if (doctorAttestation && doctorAttestation.status !== "verified") {
64
+ gaps.push(`doctor-attestation-${doctorAttestation.status}`);
65
+ }
66
+ if (tokenReservation > config.budget.maxTokens) {
67
+ gaps.push(`package-token-reservations-exceed-total:${tokenReservation}/${config.budget.maxTokens}`);
68
+ }
69
+ if (wallReservation > config.budget.maxWallSeconds) {
70
+ gaps.push(`package-wall-reservations-exceed-total:${wallReservation}/${config.budget.maxWallSeconds}`);
71
+ }
72
+ if (estimatedMaxCostUsd !== null && estimatedMaxCostUsd > config.budget.maxCostUsd) {
73
+ gaps.push(`package-cost-reservations-exceed-total:${estimatedMaxCostUsd}/${config.budget.maxCostUsd}`);
74
+ }
75
+ const outputAndRepairReservation = config.budget.maxOutputTokens + config.budget.maxRepairTokens;
76
+ for (const [stage, tokens] of Object.entries(packageTokens)) {
77
+ if (tokens < outputAndRepairReservation) {
78
+ gaps.push(`package-output-repair-reservation-exceeds-${stage}:${outputAndRepairReservation}/${tokens}`);
79
+ }
80
+ }
81
+ const embeddedStageContextReservation = config.budget.maxOutputTokens * 2;
82
+ if (embeddedStageContextReservation > config.budget.maxInputContextTokens) {
83
+ gaps.push(`embedded-stage-context-reservation-exceeds-total:${embeddedStageContextReservation}/${config.budget.maxInputContextTokens}`);
84
+ }
85
+ const recommendedDiscoverOutputTokens = requirements ? 768 + requirements.minSources * 320 : null;
86
+ if (config.mode === "production-research" &&
87
+ recommendedDiscoverOutputTokens !== null &&
88
+ config.budget.maxOutputTokens < recommendedDiscoverOutputTokens) {
89
+ gaps.push(`discover-output-reservation-below-schema-recommendation:${config.budget.maxOutputTokens}/${recommendedDiscoverOutputTokens}`);
90
+ }
91
+ const estimatedInputContextTokens = inputPlan?.inputs.reduce((sum, input) => sum + Math.ceil((input.contextBytes ?? input.bytes) / 4), 0) ?? 0;
92
+ if (estimatedInputContextTokens > config.budget.maxInputContextTokens) {
93
+ gaps.push(`input-context-reservation-exceeds-total:${estimatedInputContextTokens}/${config.budget.maxInputContextTokens}`);
94
+ }
95
+ const maxTurns = {
96
+ discover: networkCapabilities.length
97
+ ? RESEARCH_BROKER_MAX_TURNS
98
+ : RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS,
99
+ analyze: RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS,
100
+ synthesize: RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS,
101
+ review: RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS,
102
+ repair: RESEARCH_REPAIR_MAX_TURNS,
103
+ };
104
+ const preCallTokenReservations = {
105
+ discover: estimatedStageTokenReservation(config.producer, estimatedInputContextTokens, maxTurns.discover, config),
106
+ analyze: estimatedStageTokenReservation(config.producer, config.budget.maxOutputTokens, RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS, config),
107
+ synthesize: estimatedStageTokenReservation(config.producer, config.budget.maxOutputTokens * 2, RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS, config),
108
+ review: estimatedStageTokenReservation(config.reviewer, estimatedInputContextTokens + config.budget.maxOutputTokens * 3 + 2_000, RESEARCH_STRUCTURED_OUTPUT_MAX_TURNS, config),
109
+ };
110
+ for (const [stage, reservation] of Object.entries(preCallTokenReservations)) {
111
+ const configured = packageTokens[stage];
112
+ if (reservation > configured) {
113
+ gaps.push(`package-precall-reservation-exceeds-${stage}:${reservation}/${configured}`);
114
+ }
115
+ }
116
+ if (requirements) {
117
+ appendEvidencePlanGaps(gaps, requirements, inputPlan, networkCapabilities);
118
+ }
119
+ const result = {
120
+ schemaVersion: 1,
121
+ mode: config.mode,
122
+ questionSha256: sha256Text(question),
123
+ evidenceRequirements: requirements,
124
+ capabilities: networkCapabilities,
125
+ inputPlan: inputPlan === null
126
+ ? null
127
+ : {
128
+ sha256: inputPlan.sha256,
129
+ inputs: inputPlan.inputs.map((input) => ({
130
+ id: input.id,
131
+ role: input.role,
132
+ sha256: input.sha256,
133
+ bytes: input.bytes,
134
+ contextSha256: input.contextSha256,
135
+ contextBytes: input.contextBytes,
136
+ contextRanges: input.contextRanges ?? null,
137
+ dimensions: input.dimensions,
138
+ sourceType: input.sourceType,
139
+ fullText: input.fullText,
140
+ publicationDate: input.publicationDate,
141
+ })),
142
+ },
143
+ capabilityVerification,
144
+ doctorAttestation: doctorAttestation === null
145
+ ? null
146
+ : {
147
+ status: doctorAttestation.status,
148
+ errors: doctorAttestation.errors,
149
+ attestationSha256: doctorAttestation.attestation?.attestationSha256 ?? null,
150
+ checkedAt: doctorAttestation.attestation?.checkedAt ?? null,
151
+ expiresAt: doctorAttestation.attestation?.expiresAt ?? null,
152
+ },
153
+ gaps,
154
+ budget: {
155
+ tokenReservation,
156
+ wallReservation,
157
+ maxTokens: config.budget.maxTokens,
158
+ maxCostUsd: config.budget.maxCostUsd,
159
+ maxWallSeconds: config.budget.maxWallSeconds,
160
+ confirmationCostUsd: config.budget.confirmationCostUsd,
161
+ confirmationRequired: config.budget.maxCostUsd > config.budget.confirmationCostUsd,
162
+ estimatedMaxCostUsd,
163
+ maxBrokerContextTokens: config.budget.maxBrokerContextTokens,
164
+ estimatedInputContextTokens,
165
+ maxInputContextTokens: config.budget.maxInputContextTokens,
166
+ embeddedStageContextReservation,
167
+ recommendedDiscoverOutputTokens,
168
+ outputTokenLimitEnforcement: {
169
+ producer: "post-execution",
170
+ reviewer: "post-execution",
171
+ },
172
+ preCallTokenReservations,
173
+ maxTurns,
174
+ },
175
+ executionPolicy: {
176
+ producer: {
177
+ agent: config.producer.agent,
178
+ model: config.producer.model,
179
+ effort: config.producer.effort ?? null,
180
+ verbosity: config.producer.verbosity ?? null,
181
+ wrapperTargetPinned: Boolean(config.producer.wrapperTargetBinary),
182
+ turnLimitEnforcement: config.producer.agent === "claude" ? "provider" : "reservation-and-post-execution",
183
+ },
184
+ reviewer: {
185
+ agent: config.reviewer.agent,
186
+ model: config.reviewer.model,
187
+ effort: config.reviewer.effort ?? null,
188
+ verbosity: config.reviewer.verbosity ?? null,
189
+ wrapperTargetPinned: Boolean(config.reviewer.wrapperTargetBinary),
190
+ turnLimitEnforcement: config.reviewer.agent === "claude" ? "provider" : "reservation-and-post-execution",
191
+ },
192
+ },
193
+ readyToInitialize: capabilityVerification.status === "verified" &&
194
+ (config.mode === "smoke-test" || Boolean(requirements)) &&
195
+ gaps.length === 0,
196
+ };
197
+ return { ...result, preflightSha256: sha256Text(canonicalJson(result)) };
198
+ }
199
+ function estimatedStageTokenReservation(route, embeddedContextTokens, primaryMaxTurns, config) {
200
+ const protocolOverhead = RESEARCH_AGENT_PROTOCOL_OVERHEAD_TOKENS[route.agent];
201
+ const primaryPromptAndSchemaAllowance = 4_000;
202
+ const primaryInput = (protocolOverhead + embeddedContextTokens + primaryPromptAndSchemaAllowance) * primaryMaxTurns;
203
+ const repairInput = protocolOverhead +
204
+ Math.ceil((RESEARCH_MAX_REPAIR_SOURCE_BYTES + 2_048) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
205
+ return (primaryInput +
206
+ config.budget.maxOutputTokens +
207
+ repairInput * RESEARCH_REPAIR_MAX_TURNS +
208
+ config.budget.maxRepairTokens);
209
+ }
210
+ export function reservedAgentPackageCost(route, tokens, config) {
211
+ if (!route.pricing)
212
+ return 0;
213
+ const maximumOutputTokens = Math.min(tokens, config.budget.maxOutputTokens + config.budget.maxRepairTokens);
214
+ const maximumInputTokens = Math.max(0, tokens - maximumOutputTokens);
215
+ const inputRate = Math.max(route.pricing.inputUsdPerMillionTokens, route.pricing.cachedInputUsdPerMillionTokens);
216
+ return ((maximumInputTokens * inputRate +
217
+ maximumOutputTokens * route.pricing.outputUsdPerMillionTokens) /
218
+ 1_000_000);
219
+ }
220
+ function appendEvidencePlanGaps(gaps, requirements, inputPlan, networkCapabilities) {
221
+ const declaredCoverage = networkCapabilities.flatMap((capability) => capability.coverage ? [capability.coverage] : []);
222
+ const dimensions = new Set([
223
+ ...declaredCoverage.flatMap((coverage) => coverage.dimensions),
224
+ ...(inputPlan?.inputs.flatMap((input) => input.dimensions) ?? []),
225
+ ]);
226
+ const sourceTypes = new Set([
227
+ ...declaredCoverage.flatMap((coverage) => coverage.sourceTypes),
228
+ ...(inputPlan?.inputs.map((input) => input.sourceType) ?? []),
229
+ ]);
230
+ for (const dimension of requirements.dimensions) {
231
+ if (!dimensions.has(dimension))
232
+ gaps.push(`evidence-plan-dimension-uncovered:${dimension}`);
233
+ }
234
+ for (const sourceType of requirements.sourceTypes) {
235
+ if (!sourceTypes.has(sourceType)) {
236
+ gaps.push(`evidence-plan-source-type-uncovered:${sourceType}`);
237
+ }
238
+ }
239
+ const canDiscoverFullText = declaredCoverage.some((coverage) => coverage.fullText);
240
+ const canDiscoverDates = declaredCoverage.some((coverage) => coverage.publicationDates);
241
+ const plannedInputCount = inputPlan?.inputs.length ?? 0;
242
+ const plannedFullTextCount = inputPlan?.inputs.filter((input) => input.fullText).length ?? 0;
243
+ const plannedDatedCount = inputPlan?.inputs.filter((input) => input.publicationDate !== null &&
244
+ (requirements.publicationDateFrom === null ||
245
+ input.publicationDate >= requirements.publicationDateFrom) &&
246
+ (requirements.publicationDateTo === null ||
247
+ input.publicationDate <= requirements.publicationDateTo)).length ?? 0;
248
+ if (!networkCapabilities.length && plannedInputCount < requirements.minSources) {
249
+ gaps.push(`evidence-plan-min-sources-insufficient:${plannedInputCount}/${requirements.minSources}`);
250
+ }
251
+ if (requirements.minFullTextSources > plannedFullTextCount && !canDiscoverFullText) {
252
+ gaps.push(`evidence-plan-full-text-insufficient:${plannedFullTextCount}/${requirements.minFullTextSources}`);
253
+ }
254
+ if (requirements.minDatedSources > plannedDatedCount && !canDiscoverDates) {
255
+ gaps.push(`evidence-plan-dated-sources-insufficient:${plannedDatedCount}/${requirements.minDatedSources}`);
256
+ }
257
+ }
258
+ function roundMoney(value) {
259
+ return Math.round(value * 1_000_000) / 1_000_000;
260
+ }
261
+ //# sourceMappingURL=preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../../src/research/workspace/preflight.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,12 @@
1
- import type { ProjectInput, ProjectState, WorkPackage } from "./types.js";
2
- export declare function initializeProject(root: string, projectId: string, question: string): Promise<ProjectState>;
1
+ import type { ProjectEvidenceRequirements, ProjectInput, ProjectState, WorkPackage, VerifiedProjectInputPlan } from "./types.js";
2
+ export declare function initializeProject(root: string, projectId: string, question: string, evidenceRequirements?: ProjectEvidenceRequirements, budgetConfirmed?: boolean, inputPlan?: VerifiedProjectInputPlan): Promise<ProjectState>;
3
3
  export declare function addProjectInput(root: string, projectId: string, inputPath: string, role: ProjectInput["role"]): Promise<ProjectInput>;
4
4
  export declare function loadProject(root: string, projectId: string): Promise<ProjectState>;
5
5
  export declare function listProjects(root: string): Promise<ProjectState[]>;
6
6
  export declare function saveProject(root: string, project: ProjectState): Promise<void>;
7
+ export declare function retryProjectPackage(root: string, projectId: string, packageId?: string): Promise<ProjectState>;
8
+ export declare function forkProject(root: string, sourceProjectId: string, targetProjectId: string, resumeThrough?: "discover" | "analyze" | "synthesize"): Promise<ProjectState>;
7
9
  export declare function refreshProject(project: ProjectState): ProjectState;
8
10
  export declare function nextReadyPackage(project: ProjectState): WorkPackage | undefined;
9
11
  export declare function packageById(project: ProjectState, packageId: string): WorkPackage;
12
+ export declare function normalizeEvidenceRequirements(value: ProjectEvidenceRequirements): ProjectEvidenceRequirements;
@@ -1,13 +1,16 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { lstat, readdir } from "node:fs/promises";
3
- import { basename, join, resolve, sep } from "node:path";
2
+ import { cp, lstat, readdir } from "node:fs/promises";
3
+ import { basename, dirname, join, resolve, sep } from "node:path";
4
4
  import { CliError } from "../../errors.js";
5
5
  import { RESEARCH_CONTROL_DIRECTORY } from "./constants.js";
6
6
  import { appendJournalEvent } from "./journal.js";
7
- import { ensureDirectory, fileSize, readJsonFile, sha256File, workspacePaths, writeJsonAtomic, } from "./storage.js";
7
+ import { cloneProjectEvidenceReceipts } from "./evidence.js";
8
+ import { projectInputsFromPlan, reverifyProjectInputPlan } from "./input-plan.js";
9
+ import { evaluateProjectPreflight } from "./preflight.js";
10
+ import { ensureDirectory, fileRecord, fileSize, readJsonFile, sha256File, workspacePaths, writeJsonAtomic, } from "./storage.js";
8
11
  import { loadWorkspaceConfig, withWorkspaceLock } from "./workspace.js";
9
12
  const PROJECT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{2,63}$/;
10
- export async function initializeProject(root, projectId, question) {
13
+ export async function initializeProject(root, projectId, question, evidenceRequirements, budgetConfirmed = false, inputPlan) {
11
14
  validateProjectId(projectId);
12
15
  const normalizedQuestion = question.trim();
13
16
  if (normalizedQuestion.length < 8 || normalizedQuestion.length > 4000) {
@@ -28,6 +31,36 @@ export async function initializeProject(root, projectId, question) {
28
31
  });
29
32
  }
30
33
  const config = await loadWorkspaceConfig(root);
34
+ if (config.mode === "production-research" && !evidenceRequirements) {
35
+ throw new CliError("Production research requires explicit evidence requirements.", {
36
+ code: "RESEARCH_EVIDENCE_REQUIREMENTS_REQUIRED",
37
+ exitCode: 2,
38
+ });
39
+ }
40
+ if (config.mode === "production-research" &&
41
+ config.budget.maxCostUsd > config.budget.confirmationCostUsd &&
42
+ !budgetConfirmed) {
43
+ throw new CliError(`Production budget requires explicit confirmation above $${config.budget.confirmationCostUsd}.`, {
44
+ code: "RESEARCH_BUDGET_CONFIRMATION_REQUIRED",
45
+ exitCode: 2,
46
+ details: {
47
+ maxCostUsd: config.budget.maxCostUsd,
48
+ confirmationCostUsd: config.budget.confirmationCostUsd,
49
+ },
50
+ });
51
+ }
52
+ const requirements = normalizeEvidenceRequirements(evidenceRequirements ?? defaultEvidenceRequirements(config));
53
+ const admittedInputPlan = inputPlan ? await reverifyProjectInputPlan(inputPlan) : undefined;
54
+ if (config.mode === "production-research") {
55
+ const preflight = await evaluateProjectPreflight(root, normalizedQuestion, requirements, admittedInputPlan ?? null);
56
+ if (!preflight.readyToInitialize) {
57
+ throw new CliError("Production project initialization was blocked by preflight.", {
58
+ code: "RESEARCH_PREFLIGHT_BLOCKED",
59
+ exitCode: 3,
60
+ details: { gaps: preflight.gaps, preflightSha256: preflight.preflightSha256 },
61
+ });
62
+ }
63
+ }
31
64
  const now = new Date().toISOString();
32
65
  const project = {
33
66
  schemaVersion: 1,
@@ -36,9 +69,18 @@ export async function initializeProject(root, projectId, question) {
36
69
  status: "ready",
37
70
  createdAt: now,
38
71
  updatedAt: now,
39
- inputs: [],
72
+ budgetConfirmedAt: budgetConfirmed ? now : null,
73
+ inputs: admittedInputPlan ? projectInputsFromPlan(admittedInputPlan, now) : [],
74
+ evidenceRequirements: requirements,
40
75
  packages: defaultWorkPackages(config),
41
- usage: { tokens: 0, costUsd: 0, wallSeconds: 0 },
76
+ usage: {
77
+ tokens: 0,
78
+ inputTokens: 0,
79
+ cachedInputTokens: 0,
80
+ outputTokens: 0,
81
+ costUsd: 0,
82
+ wallSeconds: 0,
83
+ },
42
84
  };
43
85
  await Promise.all([
44
86
  ensureDirectory(projectRoot),
@@ -49,6 +91,13 @@ export async function initializeProject(root, projectId, question) {
49
91
  await appendJournalEvent(paths.journal, "project.initialized", projectId, {
50
92
  projectId,
51
93
  questionSha256: await hashQuestion(normalizedQuestion),
94
+ inputPlanSha256: admittedInputPlan?.sha256 ?? null,
95
+ inputs: project.inputs.map((input) => ({
96
+ id: input.id,
97
+ role: input.role,
98
+ sha256: input.sha256,
99
+ bytes: input.bytes,
100
+ })),
52
101
  });
53
102
  return project;
54
103
  });
@@ -123,10 +172,156 @@ export async function saveProject(root, project) {
123
172
  validateProjectShape(project, project.id);
124
173
  await writeJsonAtomic(join(workspacePaths(root).projects, project.id, "project.json"), project);
125
174
  }
175
+ export async function retryProjectPackage(root, projectId, packageId) {
176
+ validateProjectId(projectId);
177
+ return withWorkspaceLock(root, "project.retry", async () => {
178
+ const project = await loadProject(root, projectId);
179
+ const selected = packageId
180
+ ? packageById(project, packageId)
181
+ : project.packages.find((item) => item.status === "failed" || item.status === "retry");
182
+ if (!selected || (selected.status !== "failed" && selected.status !== "retry")) {
183
+ throw new CliError("Project retry requires a failed or retryable package.", {
184
+ code: "RESEARCH_RETRY_NOT_AVAILABLE",
185
+ exitCode: 2,
186
+ });
187
+ }
188
+ const selectedIndex = project.packages.indexOf(selected);
189
+ const previous = {
190
+ status: selected.status,
191
+ attempts: selected.attempts,
192
+ failureKind: selected.lastFailureKind,
193
+ };
194
+ for (const [index, workPackage] of project.packages.entries()) {
195
+ if (index < selectedIndex)
196
+ continue;
197
+ workPackage.status = index === selectedIndex ? "ready" : "pending";
198
+ workPackage.maxAttempts = Math.max(workPackage.maxAttempts, workPackage.attempts + 1);
199
+ workPackage.startedAt = null;
200
+ workPackage.completedAt = null;
201
+ workPackage.lastError = null;
202
+ workPackage.lastFailureKind = null;
203
+ workPackage.retryNotBefore = null;
204
+ }
205
+ project.status = "ready";
206
+ project.updatedAt = new Date().toISOString();
207
+ await saveProject(root, project);
208
+ await appendJournalEvent(workspacePaths(root).journal, "project.retry.requested", projectId, {
209
+ projectId,
210
+ packageId: selected.id,
211
+ previous,
212
+ preservedOutputs: true,
213
+ });
214
+ return project;
215
+ });
216
+ }
217
+ export async function forkProject(root, sourceProjectId, targetProjectId, resumeThrough) {
218
+ validateProjectId(sourceProjectId);
219
+ validateProjectId(targetProjectId);
220
+ if (sourceProjectId === targetProjectId) {
221
+ throw new CliError("Fork target must use a different project ID.", {
222
+ code: "RESEARCH_PROJECT_FORK_INVALID",
223
+ exitCode: 2,
224
+ });
225
+ }
226
+ return withWorkspaceLock(root, "project.fork", async () => {
227
+ const source = await loadProject(root, sourceProjectId);
228
+ const targetRoot = join(workspacePaths(root).projects, targetProjectId);
229
+ if (await lstat(targetRoot).catch(() => undefined)) {
230
+ throw new CliError(`Research project already exists: ${targetProjectId}`, {
231
+ code: "RESEARCH_PROJECT_EXISTS",
232
+ exitCode: 2,
233
+ });
234
+ }
235
+ const config = await loadWorkspaceConfig(root);
236
+ const packages = defaultWorkPackages(config);
237
+ const inheritedStages = resumeThrough
238
+ ? ["discover", "analyze", "synthesize"].slice(0, ["discover", "analyze", "synthesize"].indexOf(resumeThrough) + 1)
239
+ : [];
240
+ for (const stage of inheritedStages) {
241
+ const sourcePackage = source.packages.find((item) => item.stage === stage);
242
+ if (sourcePackage?.status !== "complete") {
243
+ throw new CliError(`Cannot fork through incomplete package: ${stage}.`, {
244
+ code: "RESEARCH_PROJECT_FORK_INVALID",
245
+ exitCode: 2,
246
+ });
247
+ }
248
+ }
249
+ const now = new Date().toISOString();
250
+ for (const workPackage of packages) {
251
+ if (inheritedStages.includes(workPackage.stage)) {
252
+ workPackage.status = "complete";
253
+ workPackage.completedAt = now;
254
+ }
255
+ }
256
+ const project = {
257
+ schemaVersion: 1,
258
+ id: targetProjectId,
259
+ question: source.question,
260
+ status: "ready",
261
+ createdAt: now,
262
+ updatedAt: now,
263
+ budgetConfirmedAt: source.budgetConfirmedAt,
264
+ inputs: source.inputs.map((input) => ({ ...input })),
265
+ evidenceRequirements: {
266
+ ...source.evidenceRequirements,
267
+ dimensions: [...source.evidenceRequirements.dimensions],
268
+ sourceTypes: [...source.evidenceRequirements.sourceTypes],
269
+ },
270
+ packages,
271
+ usage: {
272
+ tokens: 0,
273
+ inputTokens: 0,
274
+ cachedInputTokens: 0,
275
+ outputTokens: 0,
276
+ costUsd: 0,
277
+ wallSeconds: 0,
278
+ },
279
+ };
280
+ await Promise.all([
281
+ ensureDirectory(targetRoot),
282
+ ensureDirectory(join(targetRoot, "outputs")),
283
+ ensureDirectory(join(targetRoot, "runs")),
284
+ ]);
285
+ const inheritedOutputs = [];
286
+ const stageOutput = {
287
+ discover: "outputs/evidence.json",
288
+ analyze: "outputs/analysis.json",
289
+ synthesize: "outputs/report.md",
290
+ };
291
+ for (const stage of inheritedStages) {
292
+ const logicalPath = stageOutput[stage];
293
+ const sourcePath = join(workspacePaths(root).projects, sourceProjectId, logicalPath);
294
+ const record = await fileRecord(sourcePath, logicalPath);
295
+ const destination = join(targetRoot, logicalPath);
296
+ await ensureDirectory(dirname(destination));
297
+ await cp(sourcePath, destination, { errorOnExist: true, force: false });
298
+ inheritedOutputs.push(record);
299
+ }
300
+ if (inheritedStages.includes("discover")) {
301
+ await cloneProjectEvidenceReceipts(root, sourceProjectId, targetProjectId);
302
+ }
303
+ refreshProject(project);
304
+ await writeJsonAtomic(join(targetRoot, "project.json"), project);
305
+ await appendJournalEvent(workspacePaths(root).journal, "project.forked", targetProjectId, {
306
+ sourceProjectId,
307
+ targetProjectId,
308
+ resumeThrough: resumeThrough ?? null,
309
+ inheritedOutputs,
310
+ inheritedUsage: false,
311
+ });
312
+ return project;
313
+ });
314
+ }
126
315
  export function refreshProject(project) {
316
+ const now = Date.now();
127
317
  for (const workPackage of project.packages) {
128
318
  if (workPackage.status !== "pending" && workPackage.status !== "retry")
129
319
  continue;
320
+ if (workPackage.status === "retry" &&
321
+ workPackage.retryNotBefore &&
322
+ Date.parse(workPackage.retryNotBefore) > now) {
323
+ continue;
324
+ }
130
325
  const dependenciesComplete = workPackage.dependencies.every((dependency) => project.packages.find((candidate) => candidate.id === dependency)?.status === "complete");
131
326
  if (dependenciesComplete)
132
327
  workPackage.status = "ready";
@@ -178,6 +373,8 @@ function workPackage(id, stage, kind, executor, dependencies, expectedOutputs, m
178
373
  attempts: 0,
179
374
  maxAttempts,
180
375
  lastError: null,
376
+ lastFailureKind: null,
377
+ retryNotBefore: null,
181
378
  startedAt: null,
182
379
  completedAt: null,
183
380
  };
@@ -187,10 +384,15 @@ function validateProjectShape(project, expectedId) {
187
384
  project.id !== expectedId ||
188
385
  !PROJECT_ID_PATTERN.test(project.id) ||
189
386
  typeof project.question !== "string" ||
387
+ (project.budgetConfirmedAt !== null && typeof project.budgetConfirmedAt !== "string") ||
190
388
  !Array.isArray(project.inputs) ||
389
+ !isEvidenceRequirements(project.evidenceRequirements) ||
191
390
  !Array.isArray(project.packages) ||
192
391
  !project.usage ||
193
392
  typeof project.usage.tokens !== "number" ||
393
+ typeof project.usage.inputTokens !== "number" ||
394
+ typeof project.usage.cachedInputTokens !== "number" ||
395
+ typeof project.usage.outputTokens !== "number" ||
194
396
  typeof project.usage.costUsd !== "number" ||
195
397
  typeof project.usage.wallSeconds !== "number") {
196
398
  throw new CliError(`Research project state is invalid: ${expectedId}`, {
@@ -210,6 +412,88 @@ function validateProjectShape(project, expectedId) {
210
412
  });
211
413
  }
212
414
  }
415
+ function defaultEvidenceRequirements(config) {
416
+ return config.mode === "production-research"
417
+ ? {
418
+ dimensions: ["research-question"],
419
+ sourceTypes: ["primary"],
420
+ minSources: 3,
421
+ minFullTextSources: 1,
422
+ minDatedSources: 1,
423
+ publicationDateFrom: null,
424
+ publicationDateTo: null,
425
+ }
426
+ : {
427
+ dimensions: ["research-question"],
428
+ sourceTypes: [],
429
+ minSources: 1,
430
+ minFullTextSources: 0,
431
+ minDatedSources: 0,
432
+ publicationDateFrom: null,
433
+ publicationDateTo: null,
434
+ };
435
+ }
436
+ export function normalizeEvidenceRequirements(value) {
437
+ const normalized = {
438
+ dimensions: [...new Set(value.dimensions.map(normalizeRequirementId))].sort(),
439
+ sourceTypes: [...new Set(value.sourceTypes.map(normalizeRequirementId))].sort(),
440
+ minSources: value.minSources,
441
+ minFullTextSources: value.minFullTextSources,
442
+ minDatedSources: value.minDatedSources,
443
+ publicationDateFrom: value.publicationDateFrom,
444
+ publicationDateTo: value.publicationDateTo,
445
+ };
446
+ if (!isEvidenceRequirements(normalized)) {
447
+ throw new CliError("Evidence requirements are invalid.", {
448
+ code: "RESEARCH_EVIDENCE_REQUIREMENTS_INVALID",
449
+ exitCode: 2,
450
+ });
451
+ }
452
+ return normalized;
453
+ }
454
+ function isEvidenceRequirements(value) {
455
+ return (Boolean(value) &&
456
+ typeof value === "object" &&
457
+ !Array.isArray(value) &&
458
+ Array.isArray(value.dimensions) &&
459
+ value.dimensions.length > 0 &&
460
+ value.dimensions.every(validRequirementId) &&
461
+ Array.isArray(value.sourceTypes) &&
462
+ value.sourceTypes.every(validRequirementId) &&
463
+ Number.isInteger(value.minSources) &&
464
+ value.minSources > 0 &&
465
+ Number.isInteger(value.minFullTextSources) &&
466
+ value.minFullTextSources >= 0 &&
467
+ value.minFullTextSources <=
468
+ value.minSources &&
469
+ Number.isInteger(value.minDatedSources) &&
470
+ value.minDatedSources >= 0 &&
471
+ value.minDatedSources <=
472
+ value.minSources &&
473
+ validDateBoundary(value.publicationDateFrom) &&
474
+ validDateBoundary(value.publicationDateTo) &&
475
+ dateRangeOrdered(value.publicationDateFrom, value.publicationDateTo));
476
+ }
477
+ function validDateBoundary(value) {
478
+ if (value === null)
479
+ return true;
480
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
481
+ return false;
482
+ const timestamp = Date.parse(`${value}T00:00:00.000Z`);
483
+ return Number.isFinite(timestamp) && new Date(timestamp).toISOString().slice(0, 10) === value;
484
+ }
485
+ function dateRangeOrdered(from, to) {
486
+ return from === null || to === null || from <= to;
487
+ }
488
+ function normalizeRequirementId(value) {
489
+ return value
490
+ .trim()
491
+ .toLowerCase()
492
+ .replace(/[^a-z0-9._:-]+/g, "-");
493
+ }
494
+ function validRequirementId(value) {
495
+ return typeof value === "string" && /^[a-z0-9][a-z0-9._:-]{0,127}$/.test(value);
496
+ }
213
497
  function validateProjectId(projectId) {
214
498
  if (!PROJECT_ID_PATTERN.test(projectId)) {
215
499
  throw new CliError("Project ID must contain 3-64 lowercase letters, digits, or hyphens and start with a letter or digit.", { code: "RESEARCH_PROJECT_ID_INVALID", exitCode: 2 });