voratiq 0.1.0-beta.21 → 0.1.0-beta.22

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 (149) hide show
  1. package/README.md +18 -22
  2. package/dist/agents/launch/chat.d.ts +3 -1
  3. package/dist/agents/launch/chat.js +2 -0
  4. package/dist/bin.js +28 -7
  5. package/dist/cli/auto.js +1 -0
  6. package/dist/cli/contract.d.ts +26 -17
  7. package/dist/cli/contract.js +3 -1
  8. package/dist/cli/doctor.d.ts +12 -0
  9. package/dist/cli/doctor.js +115 -0
  10. package/dist/cli/list.js +4 -1
  11. package/dist/cli/operator-envelope.d.ts +19 -6
  12. package/dist/cli/operator-envelope.js +61 -1
  13. package/dist/cli/run.js +2 -0
  14. package/dist/cli/verify.d.ts +1 -1
  15. package/dist/cli/verify.js +48 -9
  16. package/dist/commands/auto/command.d.ts +1 -0
  17. package/dist/commands/auto/command.js +22 -12
  18. package/dist/commands/auto/errors.js +1 -1
  19. package/dist/commands/doctor/agents.d.ts +5 -0
  20. package/dist/commands/{init → doctor}/agents.js +37 -19
  21. package/dist/commands/doctor/command.d.ts +22 -0
  22. package/dist/commands/doctor/command.js +99 -0
  23. package/dist/commands/doctor/environment.d.ts +2 -0
  24. package/dist/commands/{init → doctor}/environment.js +38 -6
  25. package/dist/commands/{init/types.d.ts → doctor/fix-types.d.ts} +30 -9
  26. package/dist/commands/doctor/fix.d.ts +2 -0
  27. package/dist/commands/{init/command.js → doctor/fix.js} +106 -10
  28. package/dist/commands/doctor/reconcile.d.ts +2 -0
  29. package/dist/commands/doctor/reconcile.js +101 -0
  30. package/dist/commands/interactive/lifecycle.d.ts +2 -0
  31. package/dist/commands/interactive/lifecycle.js +8 -0
  32. package/dist/commands/list/command.d.ts +1 -0
  33. package/dist/commands/list/command.js +211 -352
  34. package/dist/commands/list/normalization.d.ts +56 -0
  35. package/dist/commands/list/normalization.js +317 -0
  36. package/dist/commands/message/command.d.ts +2 -1
  37. package/dist/commands/message/command.js +35 -14
  38. package/dist/commands/message/errors.d.ts +12 -3
  39. package/dist/commands/message/errors.js +19 -3
  40. package/dist/commands/reduce/command.js +16 -17
  41. package/dist/commands/reduce/errors.d.ts +2 -2
  42. package/dist/commands/reduce/errors.js +3 -3
  43. package/dist/commands/reduce/targets.js +11 -2
  44. package/dist/commands/root-launcher/command.js +12 -6
  45. package/dist/commands/run/command.d.ts +1 -0
  46. package/dist/commands/run/command.js +4 -1
  47. package/dist/commands/run/record-init.d.ts +2 -0
  48. package/dist/commands/run/record-init.js +2 -1
  49. package/dist/commands/run/spec-provenance.d.ts +37 -0
  50. package/dist/commands/run/spec-provenance.js +384 -0
  51. package/dist/commands/run/validation.d.ts +4 -0
  52. package/dist/commands/run/validation.js +25 -62
  53. package/dist/commands/spec/command.js +19 -6
  54. package/dist/commands/spec/errors.d.ts +5 -0
  55. package/dist/commands/spec/errors.js +9 -0
  56. package/dist/commands/verify/agents.d.ts +4 -2
  57. package/dist/commands/verify/agents.js +4 -11
  58. package/dist/commands/verify/command.js +15 -5
  59. package/dist/commands/verify/errors.d.ts +12 -0
  60. package/dist/commands/verify/errors.js +22 -0
  61. package/dist/commands/verify/targets.js +108 -12
  62. package/dist/competition/shared/preflight.d.ts +1 -1
  63. package/dist/competition/shared/preflight.js +15 -2
  64. package/dist/contracts/list.d.ts +129 -149
  65. package/dist/contracts/list.js +47 -99
  66. package/dist/domain/interactive/persistence/adapter.d.ts +23 -0
  67. package/dist/domain/interactive/persistence/adapter.js +42 -0
  68. package/dist/domain/message/model/types.d.ts +32 -0
  69. package/dist/domain/message/model/types.js +25 -0
  70. package/dist/domain/reduce/competition/adapter.js +21 -7
  71. package/dist/domain/reduce/competition/finalize.d.ts +7 -0
  72. package/dist/domain/reduce/competition/finalize.js +19 -0
  73. package/dist/domain/reduce/model/types.d.ts +3 -3
  74. package/dist/domain/run/competition/agents/artifacts.js +4 -2
  75. package/dist/domain/run/competition/errors.d.ts +1 -1
  76. package/dist/domain/run/competition/errors.js +4 -7
  77. package/dist/domain/run/model/types.d.ts +384 -0
  78. package/dist/domain/run/model/types.js +87 -0
  79. package/dist/domain/spec/competition/adapter.d.ts +1 -0
  80. package/dist/domain/spec/competition/adapter.js +6 -1
  81. package/dist/domain/spec/model/types.d.ts +3 -0
  82. package/dist/domain/spec/model/types.js +5 -0
  83. package/dist/domain/verify/competition/finalize.d.ts +9 -0
  84. package/dist/domain/verify/competition/finalize.js +22 -3
  85. package/dist/domain/verify/model/types.d.ts +2 -2
  86. package/dist/interactive/providers/mcp.d.ts +1 -0
  87. package/dist/interactive/providers/mcp.js +45 -7
  88. package/dist/interactive/substrate.js +20 -3
  89. package/dist/mcp/server.js +26 -9
  90. package/dist/policy/verification.js +18 -1
  91. package/dist/preflight/agents.d.ts +24 -0
  92. package/dist/preflight/agents.js +71 -0
  93. package/dist/preflight/environment.d.ts +6 -0
  94. package/dist/preflight/environment.js +17 -0
  95. package/dist/preflight/formatting.d.ts +5 -0
  96. package/dist/preflight/formatting.js +20 -0
  97. package/dist/preflight/index.d.ts +2 -0
  98. package/dist/preflight/index.js +5 -9
  99. package/dist/preflight/operator.d.ts +32 -0
  100. package/dist/preflight/operator.js +40 -0
  101. package/dist/preflight/settings.d.ts +2 -0
  102. package/dist/preflight/settings.js +17 -0
  103. package/dist/render/transcripts/interactive.d.ts +16 -0
  104. package/dist/render/transcripts/interactive.js +42 -0
  105. package/dist/render/transcripts/list.d.ts +41 -0
  106. package/dist/render/transcripts/list.js +152 -3
  107. package/dist/render/transcripts/message.d.ts +2 -1
  108. package/dist/render/transcripts/message.js +19 -20
  109. package/dist/render/transcripts/reduce.d.ts +1 -0
  110. package/dist/render/transcripts/reduce.js +21 -21
  111. package/dist/render/transcripts/root-launcher.js +2 -12
  112. package/dist/render/transcripts/run.d.ts +3 -0
  113. package/dist/render/transcripts/run.js +30 -4
  114. package/dist/render/transcripts/spec.js +5 -8
  115. package/dist/render/transcripts/verify.d.ts +5 -3
  116. package/dist/render/transcripts/verify.js +44 -31
  117. package/dist/render/utils/duration.d.ts +5 -0
  118. package/dist/render/utils/duration.js +6 -0
  119. package/dist/render/utils/runs.d.ts +1 -0
  120. package/dist/render/utils/runs.js +1 -0
  121. package/dist/render/utils/transcript-shell.d.ts +2 -1
  122. package/dist/render/utils/transcript-shell.js +19 -6
  123. package/dist/utils/errors.d.ts +2 -1
  124. package/dist/utils/errors.js +3 -1
  125. package/dist/utils/git.d.ts +1 -1
  126. package/dist/utils/git.js +25 -2
  127. package/dist/utils/list-target.d.ts +4 -0
  128. package/dist/utils/list-target.js +35 -0
  129. package/dist/utils/terminal.d.ts +1 -0
  130. package/dist/utils/terminal.js +11 -0
  131. package/dist/workspace/chat/artifacts.d.ts +7 -0
  132. package/dist/workspace/chat/artifacts.js +94 -3
  133. package/dist/workspace/errors.js +2 -2
  134. package/dist/workspace/managed-state.d.ts +32 -0
  135. package/dist/workspace/managed-state.js +103 -0
  136. package/dist/workspace/setup.js +66 -2
  137. package/dist/workspace/shim.d.ts +1 -0
  138. package/dist/workspace/shim.js +3 -3
  139. package/dist/workspace/structure.d.ts +1 -0
  140. package/dist/workspace/structure.js +1 -0
  141. package/package.json +2 -2
  142. package/dist/cli/init.d.ts +0 -15
  143. package/dist/cli/init.js +0 -70
  144. package/dist/commands/init/agents.d.ts +0 -4
  145. package/dist/commands/init/command.d.ts +0 -2
  146. package/dist/commands/init/environment.d.ts +0 -2
  147. package/dist/render/transcripts/init.d.ts +0 -7
  148. package/dist/render/transcripts/init.js +0 -83
  149. /package/dist/commands/{init/types.js → doctor/fix-types.js} +0 -0
@@ -131,6 +131,8 @@ export function buildSpecOperatorEnvelope(options) {
131
131
  });
132
132
  }
133
133
  export function buildRunOperatorEnvelope(options) {
134
+ const target = buildRunEnvelopeTarget(options.specTarget);
135
+ const alerts = buildRunEnvelopeAlerts(options.specTarget);
134
136
  return buildOperatorEnvelope({
135
137
  operator: "run",
136
138
  status: normalizeTerminalStatus(options.status),
@@ -149,10 +151,12 @@ export function buildRunOperatorEnvelope(options) {
149
151
  path: options.specPath,
150
152
  },
151
153
  ],
154
+ target,
155
+ alerts,
152
156
  });
153
157
  }
154
158
  export function buildVerifyOperatorEnvelope(options) {
155
- const status = options.selection?.state === "unresolved"
159
+ const status = options.status === "unresolved" || options.selection?.state === "unresolved"
156
160
  ? "unresolved"
157
161
  : normalizeTerminalStatus(options.status);
158
162
  const ids = {
@@ -232,6 +236,9 @@ export function buildReduceOperatorEnvelope(options) {
232
236
  if (options.target.type === "reduce") {
233
237
  ids.reductionId = options.target.id;
234
238
  }
239
+ if (options.target.type === "message") {
240
+ ids.messageId = options.target.id;
241
+ }
235
242
  return buildOperatorEnvelope({
236
243
  operator: "reduce",
237
244
  status: normalizeTerminalStatus(options.status),
@@ -331,6 +338,59 @@ export function buildPruneOperatorEnvelope(options) {
331
338
  : [],
332
339
  });
333
340
  }
341
+ function buildRunEnvelopeTarget(specTarget) {
342
+ if (specTarget?.kind === "spec") {
343
+ return {
344
+ kind: "spec",
345
+ sessionId: specTarget.sessionId,
346
+ ...(specTarget.provenance
347
+ ? buildRunEnvelopeProvenanceFields(specTarget.provenance)
348
+ : {}),
349
+ };
350
+ }
351
+ if (specTarget?.kind === "file" &&
352
+ specTarget.provenance?.source?.sessionId &&
353
+ specTarget.provenance.lineage === "invalid") {
354
+ return {
355
+ kind: "spec",
356
+ sessionId: specTarget.provenance.source.sessionId,
357
+ ...buildRunEnvelopeProvenanceFields(specTarget.provenance),
358
+ };
359
+ }
360
+ return undefined;
361
+ }
362
+ function buildRunEnvelopeAlerts(specTarget) {
363
+ if (specTarget?.provenance?.lineage !== "invalid") {
364
+ return undefined;
365
+ }
366
+ return [
367
+ {
368
+ level: "warn",
369
+ message: specTarget.provenance.issueCode === "stale_source"
370
+ ? "Run spec ancestry metadata referenced a stale or mismatched spec artifact."
371
+ : "Run spec ancestry metadata was malformed and was ignored.",
372
+ },
373
+ ];
374
+ }
375
+ function buildRunEnvelopeProvenanceFields(provenance) {
376
+ return {
377
+ lineage: provenance.lineage,
378
+ ...(provenance.lineage === "invalid"
379
+ ? { issueCode: provenance.issueCode }
380
+ : {}),
381
+ ...("currentContentHash" in provenance &&
382
+ typeof provenance.currentContentHash === "string"
383
+ ? { currentContentHash: provenance.currentContentHash }
384
+ : {}),
385
+ ...(provenance.source
386
+ ? {
387
+ source: {
388
+ ...provenance.source,
389
+ },
390
+ }
391
+ : {}),
392
+ };
393
+ }
334
394
  export function writeOperatorResultEnvelope(envelope, exitCode) {
335
395
  process.stdout.write(`${JSON.stringify(envelope)}\n`);
336
396
  if (typeof exitCode === "number") {
package/dist/cli/run.js CHANGED
@@ -61,6 +61,7 @@ export async function runRunCommand(options) {
61
61
  const report = await executeRunCommand({
62
62
  root,
63
63
  runsFilePath: workspacePaths.runsFile,
64
+ specsFilePath: workspacePaths.specsFile,
64
65
  specAbsolutePath: absolutePath,
65
66
  specDisplayPath: displayPath,
66
67
  agentIds,
@@ -131,6 +132,7 @@ export function createRunCommand() {
131
132
  writeOperatorResultEnvelope(buildRunOperatorEnvelope({
132
133
  runId: result.report.runId,
133
134
  specPath: result.report.spec.path,
135
+ specTarget: result.report.spec.target,
134
136
  status: result.report.status,
135
137
  }), result.exitCode);
136
138
  return;
@@ -22,7 +22,7 @@ export interface VerifyCommandOptions {
22
22
  }
23
23
  export interface VerifyCommandResult {
24
24
  verificationId: string;
25
- status: VerificationRecord["status"];
25
+ status: VerificationRecord["status"] | "unresolved";
26
26
  target: VerificationRecord["target"];
27
27
  body: string;
28
28
  exitCode?: number;
@@ -23,6 +23,22 @@ import { resolveWorkspacePath, VORATIQ_MESSAGE_FILE, VORATIQ_REDUCTION_FILE, VOR
23
23
  import { parseVerifyExecutionCommandOptions } from "./contract.js";
24
24
  import { buildVerifyOperatorEnvelope, createSilentCliWriter, writeOperatorResultEnvelope, } from "./operator-envelope.js";
25
25
  import { writeCommandOutput } from "./output.js";
26
+ function resolveVerifyManualActionMessage(options) {
27
+ const { targetKind, status, selection, selectedSpecPath } = options;
28
+ if (status !== "succeeded") {
29
+ return undefined;
30
+ }
31
+ if (targetKind === "spec" && !selectedSpecPath) {
32
+ return "Verification did not select a spec path; manual review required.";
33
+ }
34
+ if (selection?.decision.state !== "unresolved") {
35
+ return undefined;
36
+ }
37
+ if (targetKind === "run") {
38
+ return "Verification did not produce a resolvable candidate; manual selection required.";
39
+ }
40
+ return "Verification did not produce a resolvable result; manual review required.";
41
+ }
26
42
  export async function runVerifyCommand(options) {
27
43
  const { target, agentIds, agentOverrideFlag, profile, maxParallel, extraContext, json = false, suppressHint, stdout, stderr, writeOutput, } = options;
28
44
  const effectiveWriteOutput = json
@@ -97,10 +113,30 @@ export async function runVerifyCommand(options) {
97
113
  return undefined;
98
114
  });
99
115
  const selectionWarnings = (selection?.warnings ?? []).map((warning) => `Warning: ${warning}`);
116
+ const selectedSpecPath = execution.record.target.kind === "spec" &&
117
+ typeof execution.record.target.specPath === "string"
118
+ ? execution.record.target.specPath
119
+ : undefined;
120
+ const manualActionMessage = resolveVerifyManualActionMessage({
121
+ targetKind: execution.record.target.kind,
122
+ status: execution.record.status,
123
+ selection,
124
+ selectedSpecPath,
125
+ });
100
126
  const warningMessage = [
101
127
  ...selectionWarnings,
102
128
  ...(selectionPolicyWarning ? [selectionPolicyWarning] : []),
129
+ ...(manualActionMessage ? [manualActionMessage] : []),
103
130
  ].join("\n");
131
+ const displayStatus = selection?.decision.state === "unresolved"
132
+ ? "unresolved"
133
+ : execution.record.status;
134
+ if (displayStatus === "unresolved" && !json) {
135
+ renderer.complete("unresolved", {
136
+ startedAt: execution.record.startedAt,
137
+ completedAt: execution.record.completedAt,
138
+ });
139
+ }
104
140
  const recommendedRunAgent = execution.record.target.kind === "run" &&
105
141
  selection !== undefined &&
106
142
  selection.decision.state === "resolvable"
@@ -109,9 +145,10 @@ export async function runVerifyCommand(options) {
109
145
  const hintMessage = suppressHint ||
110
146
  selectionPolicyWarning ||
111
147
  execution.record.target.kind !== "run" ||
112
- execution.record.status !== "succeeded"
148
+ execution.record.status !== "succeeded" ||
149
+ selection?.decision.state !== "resolvable"
113
150
  ? undefined
114
- : `To apply a solution:\n voratiq apply --run ${execution.record.target.sessionId} --agent ${recommendedRunAgent ?? "<agent-id>"}`;
151
+ : `To apply a solution:\n voratiq apply --run ${execution.record.target.sessionId} --agent ${recommendedRunAgent}`;
115
152
  const outputPath = normalizePathForDisplay(relativeToRoot(root, resolveWorkspacePath(root, VORATIQ_VERIFICATION_SESSIONS_DIR, execution.verificationId)));
116
153
  const body = renderVerifyTranscript({
117
154
  verificationId: execution.verificationId,
@@ -123,7 +160,7 @@ export async function runVerifyCommand(options) {
123
160
  }) ?? "—",
124
161
  workspacePath: normalizePathForDisplay(relativeToRoot(root, resolveWorkspacePath(root, VORATIQ_VERIFICATION_SESSIONS_DIR, execution.verificationId))),
125
162
  target: execution.record.target,
126
- status: execution.record.status,
163
+ status: displayStatus,
127
164
  methods: methodBlocks,
128
165
  suppressHint,
129
166
  ...(warningMessage ? { warningMessage } : {}),
@@ -133,15 +170,17 @@ export async function runVerifyCommand(options) {
133
170
  });
134
171
  return {
135
172
  verificationId: execution.verificationId,
136
- status: execution.record.status,
173
+ status: displayStatus,
137
174
  target: execution.record.target,
138
175
  body,
139
- exitCode: execution.record.status === "succeeded" ? 0 : 1,
176
+ exitCode: execution.record.status === "succeeded" &&
177
+ selection?.decision.state !== "unresolved" &&
178
+ !(execution.record.target.kind === "spec" &&
179
+ typeof selectedSpecPath !== "string")
180
+ ? 0
181
+ : 1,
140
182
  outputPath,
141
- ...(execution.record.target.kind === "spec" &&
142
- typeof execution.record.target.specPath === "string"
143
- ? { selectedSpecPath: execution.record.target.specPath }
144
- : {}),
183
+ ...(selectedSpecPath ? { selectedSpecPath } : {}),
145
184
  selection,
146
185
  ...(warningMessage ? { warningMessage } : {}),
147
186
  };
@@ -69,6 +69,7 @@ export interface AutoVerifyStageResult {
69
69
  selectedSpecPath?: string;
70
70
  selection?: SelectionDecision;
71
71
  selectionWarnings?: readonly string[];
72
+ warningMessage?: string;
72
73
  }
73
74
  export interface AutoApplyStageInput {
74
75
  runId: string;
@@ -101,12 +101,7 @@ export async function executeAutoCommand(options, dependencies) {
101
101
  stderr: specVerifyResult.stderr,
102
102
  exitCode: specVerifyResult.exitCode,
103
103
  });
104
- if (specVerifyResult.exitCode === 1) {
105
- specStatus = "failed";
106
- specDetail = "One or more spec verifiers failed.";
107
- hardFailure = true;
108
- }
109
- else if (typeof specVerifyResult.selectedSpecPath === "string" &&
104
+ if (typeof specVerifyResult.selectedSpecPath === "string" &&
110
105
  specVerifyResult.selectedSpecPath.trim().length > 0) {
111
106
  resolvedSpecPath = specVerifyResult.selectedSpecPath;
112
107
  specPath = specVerifyResult.selectedSpecPath;
@@ -117,7 +112,7 @@ export async function executeAutoCommand(options, dependencies) {
117
112
  "Spec verification returned a resolvable selection without a selected spec path.";
118
113
  hardFailure = true;
119
114
  }
120
- else {
115
+ else if (specVerifyResult.selection) {
121
116
  const specSelectionDisposition = classifyAutoVerificationSelection({
122
117
  targetKind: "spec",
123
118
  selection: specVerifyResult.selection,
@@ -128,6 +123,13 @@ export async function executeAutoCommand(options, dependencies) {
128
123
  specDetail = specSelectionDisposition.detail;
129
124
  markActionRequired(specSelectionDisposition.detail);
130
125
  }
126
+ else if (specVerifyResult.exitCode === 1) {
127
+ specStatus = "failed";
128
+ specDetail =
129
+ specVerifyResult.warningMessage?.trim() ||
130
+ "Spec verification did not produce any successful verifier results.";
131
+ hardFailure = true;
132
+ }
131
133
  }
132
134
  catch (error) {
133
135
  specStatus = "failed";
@@ -215,11 +217,6 @@ export async function executeAutoCommand(options, dependencies) {
215
217
  });
216
218
  verifyStatus = "succeeded";
217
219
  verifySelection = verifyResult.selection;
218
- if (verifyResult.exitCode === 1) {
219
- verifyStatus = "failed";
220
- verifyDetail = "One or more verifiers failed.";
221
- hardFailure = true;
222
- }
223
220
  recordEvent({
224
221
  kind: "body",
225
222
  body: verifyResult.body,
@@ -234,6 +231,12 @@ export async function executeAutoCommand(options, dependencies) {
234
231
  separateWithDivider: bodyOutputEmitted,
235
232
  });
236
233
  }
234
+ if (options.apply === true &&
235
+ (verifyResult.selectionWarnings?.length ?? 0) > 0) {
236
+ const warningDetail = "Verification reported warnings for the selected candidate; automatic apply halted. Review the verify output and apply manually if appropriate.";
237
+ verifyDetail = warningDetail;
238
+ markActionRequired(warningDetail);
239
+ }
237
240
  if (verifySelection?.state === "unresolved") {
238
241
  const verifySelectionDisposition = classifyAutoVerificationSelection({
239
242
  targetKind: "run",
@@ -247,6 +250,13 @@ export async function executeAutoCommand(options, dependencies) {
247
250
  },
248
251
  });
249
252
  }
253
+ else if (verifyResult.exitCode === 1) {
254
+ verifyStatus = "failed";
255
+ verifyDetail =
256
+ verifyResult.warningMessage?.trim() ||
257
+ "Verification did not produce any successful verifier results.";
258
+ hardFailure = true;
259
+ }
250
260
  }
251
261
  catch (error) {
252
262
  verifyStatus = "failed";
@@ -12,7 +12,7 @@ export class AutoPreflightError extends CliError {
12
12
  }
13
13
  });
14
14
  super("Preflight failed. Aborting auto.", detailLines, [
15
- "Run `voratiq init` from the repository root, then retry.",
15
+ "Run `voratiq doctor --fix` to repair workspace setup.",
16
16
  ]);
17
17
  this.name = "AutoPreflightError";
18
18
  }
@@ -0,0 +1,5 @@
1
+ import { type AgentPreset } from "../../workspace/templates.js";
2
+ import type { AgentInitSummary, DoctorBootstrapConfigureOptions } from "./fix-types.js";
3
+ export declare const AGENTS_CONFIG_DISPLAY_PATH: string;
4
+ export declare function bootstrapDoctorAgents(root: string, preset: AgentPreset, options: DoctorBootstrapConfigureOptions): Promise<AgentInitSummary>;
5
+ export declare function reconcileManagedDoctorAgents(root: string): Promise<AgentInitSummary>;
@@ -1,21 +1,35 @@
1
1
  import { getAgentDefaultId, getSupportedAgentDefaults, } from "../../configs/agents/defaults.js";
2
2
  import { readAgentsConfig } from "../../configs/agents/loader.js";
3
3
  import { detectBinary } from "../../utils/binaries.js";
4
- import { isDefaultYamlTemplate, loadYamlConfig, persistYamlConfig, } from "../../utils/yaml.js";
4
+ import { isDefaultYamlTemplate, loadYamlConfig, writeConfigIfChanged, } from "../../utils/yaml.js";
5
+ import { isManagedAgentsFingerprintMatch, readManagedState, } from "../../workspace/managed-state.js";
5
6
  import { formatWorkspacePath, resolveWorkspacePath, VORATIQ_AGENTS_FILE, } from "../../workspace/structure.js";
6
7
  import { buildDefaultAgentsTemplate, serializeAgentsConfigEntries, } from "../../workspace/templates.js";
7
8
  export const AGENTS_CONFIG_DISPLAY_PATH = formatWorkspacePath(VORATIQ_AGENTS_FILE);
8
- export async function configureAgents(root, preset, options) {
9
+ export async function bootstrapDoctorAgents(root, preset, options) {
9
10
  void preset;
10
11
  void options;
11
- const filePath = resolveWorkspacePath(root, VORATIQ_AGENTS_FILE);
12
12
  const defaultTemplate = buildDefaultAgentsTemplate();
13
+ return configureAgentsWithMode(root, defaultTemplate, "bootstrap");
14
+ }
15
+ export async function reconcileManagedDoctorAgents(root) {
16
+ const defaultTemplate = buildDefaultAgentsTemplate();
17
+ return configureAgentsWithMode(root, defaultTemplate, "reconcile");
18
+ }
19
+ async function configureAgentsWithMode(root, defaultTemplate, mode) {
20
+ const filePath = resolveWorkspacePath(root, VORATIQ_AGENTS_FILE);
13
21
  const loadResult = await loadYamlConfig(filePath, readAgentsConfig);
14
22
  const defaultStatus = isDefaultYamlTemplate(loadResult.snapshot, defaultTemplate);
15
23
  const configCreated = !loadResult.snapshot.exists;
16
- const lifecycle = scanWorkspaceForAgentDefaults(loadResult.config, defaultStatus, getSupportedAgentDefaults());
24
+ const managedState = mode === "reconcile" ? await readManagedState(root) : undefined;
25
+ const managed = mode === "reconcile"
26
+ ? !loadResult.snapshot.exists ||
27
+ isManagedAgentsFingerprintMatch(managedState?.configs.agents, loadResult.snapshot.content) ||
28
+ defaultStatus
29
+ : defaultStatus;
30
+ const lifecycle = scanWorkspaceForAgentDefaults(loadResult.config, mode, getSupportedAgentDefaults());
17
31
  const detectedProviders = collectDetectedProviders(lifecycle.templates);
18
- if (!defaultStatus && loadResult.snapshot.exists) {
32
+ if (!managed && loadResult.snapshot.exists) {
19
33
  return buildAgentSummary({
20
34
  entries: loadResult.config.agents,
21
35
  zeroDetections: detectedProviders.length === 0,
@@ -23,16 +37,14 @@ export async function configureAgents(root, preset, options) {
23
37
  providerEnablementPrompted: false,
24
38
  configCreated,
25
39
  configUpdated: false,
40
+ managed: false,
26
41
  });
27
42
  }
28
43
  const snapshotResult = finalizeAgentConfigSnapshot(lifecycle);
29
- const updated = await persistYamlConfig({
30
- filePath,
31
- serialized: snapshotResult.serialized,
32
- original: loadResult.snapshot,
33
- defaultTemplate,
34
- isDefaultTemplate: defaultStatus,
35
- });
44
+ const previousNormalized = loadResult.snapshot.exists
45
+ ? loadResult.snapshot.normalized
46
+ : "__missing__";
47
+ const updated = await writeConfigIfChanged(filePath, snapshotResult.serialized, previousNormalized);
36
48
  return buildAgentSummary({
37
49
  entries: snapshotResult.entries,
38
50
  zeroDetections: lifecycle.zeroDetections,
@@ -40,9 +52,10 @@ export async function configureAgents(root, preset, options) {
40
52
  providerEnablementPrompted: false,
41
53
  configCreated,
42
54
  configUpdated: updated,
55
+ managed: true,
43
56
  });
44
57
  }
45
- function scanWorkspaceForAgentDefaults(config, isDefaultTemplate, templates) {
58
+ function scanWorkspaceForAgentDefaults(config, mode, templates) {
46
59
  const templatesById = new Map();
47
60
  for (const entry of config.agents) {
48
61
  templatesById.set(entry.id, entry);
@@ -56,9 +69,7 @@ function scanWorkspaceForAgentDefaults(config, isDefaultTemplate, templates) {
56
69
  }));
57
70
  for (const { template, templateId } of templateEntries) {
58
71
  templateIds.add(templateId);
59
- const existing = isDefaultTemplate
60
- ? undefined
61
- : templatesById.get(templateId);
72
+ const existing = templatesById.get(templateId);
62
73
  let detectedBinary = detectedBinaryByProvider.get(template.provider);
63
74
  if (!detectedBinaryByProvider.has(template.provider)) {
64
75
  detectedBinary = detectBinary(template.provider);
@@ -66,8 +77,14 @@ function scanWorkspaceForAgentDefaults(config, isDefaultTemplate, templates) {
66
77
  }
67
78
  const baseEntry = existing ?? buildEntryFromTemplate(template, templateId);
68
79
  const entry = cloneAgentEntry(baseEntry);
69
- entry.binary = detectedBinary ?? "";
70
- entry.enabled = entry.enabled !== false;
80
+ if (hasBinary(detectedBinary)) {
81
+ entry.binary = detectedBinary ?? "";
82
+ entry.enabled = true;
83
+ }
84
+ else if (!existing || mode === "bootstrap") {
85
+ entry.binary = "";
86
+ entry.enabled = false;
87
+ }
71
88
  templateStates.push({
72
89
  templateId,
73
90
  template,
@@ -142,7 +159,7 @@ function finalizeAgentConfigSnapshot(state) {
142
159
  return { entries, serialized };
143
160
  }
144
161
  function buildAgentSummary(options) {
145
- const { entries, zeroDetections, detectedProviders, providerEnablementPrompted, configCreated, configUpdated, } = options;
162
+ const { entries, zeroDetections, detectedProviders, providerEnablementPrompted, configCreated, configUpdated, managed, } = options;
146
163
  const enabledAgents = entries
147
164
  .filter((entry) => entry.enabled !== false)
148
165
  .map((entry) => entry.id);
@@ -155,6 +172,7 @@ function buildAgentSummary(options) {
155
172
  providerEnablementPrompted,
156
173
  configCreated,
157
174
  configUpdated,
175
+ managed,
158
176
  };
159
177
  }
160
178
  function hasBinary(binary) {
@@ -0,0 +1,22 @@
1
+ import { executeDoctorBootstrap } from "./fix.js";
2
+ import type { DoctorReconcileResult } from "./fix-types.js";
3
+ export interface DoctorDiagnosisResult {
4
+ readonly healthy: boolean;
5
+ readonly issueLines: readonly string[];
6
+ }
7
+ export type DoctorFixMode = "bootstrap-workspace" | "repair-and-reconcile";
8
+ export interface DoctorFixResult {
9
+ readonly mode: DoctorFixMode;
10
+ readonly reconcileResult?: DoctorReconcileResult;
11
+ }
12
+ export interface ExecuteDoctorDiagnosisInput {
13
+ readonly root: string;
14
+ }
15
+ export interface ExecuteDoctorFixInput {
16
+ readonly root: string;
17
+ readonly mode?: DoctorFixMode;
18
+ readonly bootstrapOptions?: Pick<Parameters<typeof executeDoctorBootstrap>[0], "preset" | "interactive" | "assumeYes" | "confirm" | "prompt">;
19
+ }
20
+ export declare function executeDoctorDiagnosis(input: ExecuteDoctorDiagnosisInput): Promise<DoctorDiagnosisResult>;
21
+ export declare function resolveDoctorFixMode(root: string): Promise<DoctorFixMode>;
22
+ export declare function executeDoctorFix(input: ExecuteDoctorFixInput): Promise<DoctorFixResult>;
@@ -0,0 +1,99 @@
1
+ import { formatPreflightIssueLines } from "../../competition/shared/preflight.js";
2
+ import { EnvironmentConfigParseError, MissingEnvironmentConfigError, } from "../../configs/environment/errors.js";
3
+ import { loadEnvironmentConfig } from "../../configs/environment/loader.js";
4
+ import { prepareConfiguredOperatorReadiness } from "../../preflight/operator.js";
5
+ import { toErrorMessage } from "../../utils/errors.js";
6
+ import { pathExists } from "../../utils/fs.js";
7
+ import { WorkspaceError, WorkspaceMissingEntryError, } from "../../workspace/errors.js";
8
+ import { repairWorkspaceStructure, validateWorkspace, } from "../../workspace/setup.js";
9
+ import { formatWorkspacePath, resolveWorkspacePath, } from "../../workspace/structure.js";
10
+ import { executeDoctorBootstrap } from "./fix.js";
11
+ import { executeDoctorReconcile } from "./reconcile.js";
12
+ const DOCTOR_PREFLIGHT_UNLABELED_AGENT_IDS = ["settings"];
13
+ export async function executeDoctorDiagnosis(input) {
14
+ const { root } = input;
15
+ const issueLines = [];
16
+ const workspacePresent = await pathExists(resolveWorkspacePath(root));
17
+ if (!workspacePresent) {
18
+ const missingWorkspaceIssue = new WorkspaceMissingEntryError(`${formatWorkspacePath()}/`);
19
+ issueLines.push(`- ${missingWorkspaceIssue.headline}`);
20
+ return {
21
+ healthy: false,
22
+ issueLines,
23
+ };
24
+ }
25
+ try {
26
+ await validateWorkspace(root);
27
+ }
28
+ catch (error) {
29
+ issueLines.push(...formatDoctorIssueLines(error));
30
+ return {
31
+ healthy: false,
32
+ issueLines,
33
+ };
34
+ }
35
+ try {
36
+ const diagnostics = await prepareConfiguredOperatorReadiness({ root });
37
+ if (diagnostics.noAgentsEnabled) {
38
+ issueLines.push("- No agents are enabled in `agents.yaml`.");
39
+ }
40
+ issueLines.push(...formatPreflightIssueLines(diagnostics.issues, {
41
+ unlabeledAgentIds: DOCTOR_PREFLIGHT_UNLABELED_AGENT_IDS,
42
+ }));
43
+ }
44
+ catch (error) {
45
+ issueLines.push(...formatDoctorIssueLines(error));
46
+ }
47
+ try {
48
+ loadEnvironmentConfig({ root });
49
+ }
50
+ catch (error) {
51
+ if (error instanceof MissingEnvironmentConfigError) {
52
+ issueLines.push(`- ${error.headline}`);
53
+ }
54
+ else if (error instanceof EnvironmentConfigParseError) {
55
+ issueLines.push(`- ${error.headline}`);
56
+ }
57
+ else {
58
+ issueLines.push(`- ${toErrorMessage(error)}`);
59
+ }
60
+ }
61
+ return {
62
+ healthy: issueLines.length === 0,
63
+ issueLines,
64
+ };
65
+ }
66
+ export async function resolveDoctorFixMode(root) {
67
+ const workspacePresent = await pathExists(resolveWorkspacePath(root));
68
+ return workspacePresent ? "repair-and-reconcile" : "bootstrap-workspace";
69
+ }
70
+ export async function executeDoctorFix(input) {
71
+ const mode = input.mode ?? (await resolveDoctorFixMode(input.root));
72
+ if (mode === "bootstrap-workspace") {
73
+ const bootstrapOptions = input.bootstrapOptions;
74
+ await executeDoctorBootstrap({
75
+ root: input.root,
76
+ preset: bootstrapOptions?.preset ?? "pro",
77
+ interactive: bootstrapOptions?.interactive ?? false,
78
+ assumeYes: bootstrapOptions?.assumeYes,
79
+ confirm: bootstrapOptions?.confirm,
80
+ prompt: bootstrapOptions?.prompt,
81
+ });
82
+ return { mode };
83
+ }
84
+ await repairWorkspaceStructure(input.root);
85
+ const reconcileResult = await executeDoctorReconcile({ root: input.root });
86
+ return {
87
+ mode,
88
+ reconcileResult,
89
+ };
90
+ }
91
+ function formatDoctorIssueLines(error) {
92
+ if (error instanceof WorkspaceError) {
93
+ return [`- ${error.headline}`];
94
+ }
95
+ if (error instanceof Error && error.message.length > 0) {
96
+ return [`- ${error.message}`];
97
+ }
98
+ return [`- ${toErrorMessage(error)}`];
99
+ }
@@ -0,0 +1,2 @@
1
+ import type { DoctorBootstrapConfigureOptions, EnvironmentInitSummary } from "./fix-types.js";
2
+ export declare function reconcileDoctorEnvironment(root: string, options: DoctorBootstrapConfigureOptions): Promise<EnvironmentInitSummary>;
@@ -1,15 +1,16 @@
1
1
  import { detectEnvironmentConfig } from "../../configs/environment/detect.js";
2
+ import { EnvironmentConfigParseError } from "../../configs/environment/errors.js";
2
3
  import { readEnvironmentConfig, serializeEnvironmentConfig, } from "../../configs/environment/loader.js";
3
4
  import { getNodeDependencyRoots, getPythonEnvironmentPath, isNodeEnvironmentDisabled, isPythonEnvironmentDisabled, } from "../../configs/environment/types.js";
4
- import { loadYamlConfig, persistYamlConfig } from "../../utils/yaml.js";
5
+ import { persistYamlConfig, readConfigSnapshot } from "../../utils/yaml.js";
5
6
  import { formatWorkspacePath, resolveWorkspacePath, VORATIQ_ENVIRONMENT_FILE, } from "../../workspace/structure.js";
6
7
  import { buildDefaultEnvironmentTemplate } from "../../workspace/templates.js";
7
8
  const ENVIRONMENT_CONFIG_DISPLAY_PATH = formatWorkspacePath(VORATIQ_ENVIRONMENT_FILE);
8
- export async function configureEnvironment(root, options) {
9
+ export async function reconcileDoctorEnvironment(root, options) {
9
10
  const filePath = resolveWorkspacePath(root, VORATIQ_ENVIRONMENT_FILE);
10
11
  const defaultTemplate = buildDefaultEnvironmentTemplate();
11
- const loadResult = await loadYamlConfig(filePath, readEnvironmentConfig);
12
- const existingConfig = loadResult.config;
12
+ const originalSnapshot = await readConfigSnapshot(filePath);
13
+ const existingConfig = resolveExistingConfig(originalSnapshot);
13
14
  const detection = await detectEnvironmentConfig({
14
15
  root,
15
16
  interactive: options.interactive,
@@ -20,17 +21,48 @@ export async function configureEnvironment(root, options) {
20
21
  const configUpdated = await persistYamlConfig({
21
22
  filePath,
22
23
  serialized: finalSerialized,
23
- original: loadResult.snapshot,
24
+ original: shouldRewriteFromScratch(originalSnapshot)
25
+ ? { content: "", normalized: "", exists: false }
26
+ : originalSnapshot,
24
27
  defaultTemplate,
25
28
  });
26
29
  return {
27
30
  configPath: ENVIRONMENT_CONFIG_DISPLAY_PATH,
28
31
  detectedEntries: describeEnvironmentEntries(mergedConfig),
29
- configCreated: !loadResult.snapshot.exists,
32
+ configCreated: !originalSnapshot.exists,
30
33
  configUpdated,
31
34
  config: mergedConfig,
32
35
  };
33
36
  }
37
+ function resolveExistingConfig(snapshot) {
38
+ if (!snapshot.exists) {
39
+ return {};
40
+ }
41
+ try {
42
+ return readEnvironmentConfig(snapshot.content);
43
+ }
44
+ catch (error) {
45
+ if (error instanceof EnvironmentConfigParseError) {
46
+ return {};
47
+ }
48
+ throw error;
49
+ }
50
+ }
51
+ function shouldRewriteFromScratch(snapshot) {
52
+ if (!snapshot.exists) {
53
+ return false;
54
+ }
55
+ try {
56
+ readEnvironmentConfig(snapshot.content);
57
+ return false;
58
+ }
59
+ catch (error) {
60
+ if (error instanceof EnvironmentConfigParseError) {
61
+ return true;
62
+ }
63
+ throw error;
64
+ }
65
+ }
34
66
  function mergeEnvironmentConfig(existing, detected) {
35
67
  const merged = { ...existing };
36
68
  if (!isNodeEnvironmentDisabled(merged)) {