melchizedek-agents 1.7.0 → 1.10.0

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 (57) hide show
  1. package/CHANGELOG.md +161 -1
  2. package/DOCUMENTATION.md +77 -10
  3. package/README.md +2 -0
  4. package/config/agents/syndicate.schema.json +69 -0
  5. package/config/agents/syndicateSchema.yaml +17 -0
  6. package/db/hardening.sql +3 -1
  7. package/db/migrations/0016_span_tree.sql +52 -0
  8. package/db/migrations/0017_cache_pricing.sql +146 -0
  9. package/db/migrations/0018_queue_view.sql +82 -0
  10. package/db/telemetry.sql +36 -8
  11. package/dist/lib/a2a/app.d.ts +26 -4
  12. package/dist/lib/a2a/app.js +41 -6
  13. package/dist/lib/a2a/executor.d.ts +28 -1
  14. package/dist/lib/a2a/executor.js +65 -2
  15. package/dist/lib/a2a/feedback.d.ts +121 -0
  16. package/dist/lib/a2a/feedback.js +219 -0
  17. package/dist/lib/a2a/policy.d.ts +55 -5
  18. package/dist/lib/a2a/policy.js +56 -12
  19. package/dist/lib/compile.d.ts +2 -0
  20. package/dist/lib/compile.js +2 -0
  21. package/dist/lib/compileNative.js +2 -0
  22. package/dist/lib/doctor.d.ts +10 -0
  23. package/dist/lib/doctor.js +33 -4
  24. package/dist/lib/index.d.ts +6 -2
  25. package/dist/lib/index.js +3 -0
  26. package/dist/lib/loadSyndicate.d.ts +14 -0
  27. package/dist/lib/loadSyndicate.js +22 -5
  28. package/dist/lib/memory/providers.js +7 -3
  29. package/dist/lib/observability/lineage.d.ts +19 -0
  30. package/dist/lib/observability/lineage.js +31 -0
  31. package/dist/lib/observability/metadata.d.ts +69 -0
  32. package/dist/lib/observability/metadata.js +153 -0
  33. package/dist/lib/observability/otlpFilter.js +1 -0
  34. package/dist/lib/observability/supabaseSpanExporter.d.ts +31 -2
  35. package/dist/lib/observability/supabaseSpanExporter.js +45 -6
  36. package/dist/lib/observability/tracer.d.ts +17 -1
  37. package/dist/lib/observability/tracer.js +61 -3
  38. package/dist/lib/project.d.ts +116 -0
  39. package/dist/lib/project.js +274 -0
  40. package/dist/lib/runtime/native/compaction.js +7 -3
  41. package/dist/lib/runtime/native/request.d.ts +2 -0
  42. package/dist/lib/runtime/native/step.js +1 -1
  43. package/dist/lib/runtime/native/telemetry.d.ts +10 -3
  44. package/dist/lib/runtime/native/telemetry.js +49 -4
  45. package/dist/lib/runtime/syndicateTurn.js +5 -1
  46. package/dist/lib/storage/postgres/index.d.ts +10 -0
  47. package/dist/lib/storage/postgres/index.js +6 -1
  48. package/dist/lib/syndicateSchema.d.ts +5 -0
  49. package/dist/lib/syndicateSchema.js +13 -0
  50. package/dist/lib/tools/taskTools.d.ts +6 -0
  51. package/dist/lib/tools/taskTools.js +24 -5
  52. package/dist/lib/workflow/toolNode.d.ts +1 -0
  53. package/dist/lib/workflow/toolNode.js +1 -1
  54. package/dist/scripts/a2a_server.js +3 -3
  55. package/dist/scripts/doctor.js +2 -1
  56. package/dist/scripts/mcp_server.js +2 -1
  57. package/package.json +6 -4
@@ -13,7 +13,11 @@
13
13
  * tool calls WITH their responses, the ids that join it to
14
14
  * the session (session_id, invocation_id) and the task,
15
15
  * and provenance (config_hash, engine_version).
16
- * adk_telemetry one row per span — llm.request and root spans, raw.
16
+ * adk_telemetry one row per span the ledger keeps — the turn's root, each
17
+ * agent run, model step and provider call, tool call,
18
+ * script run and call, workflow walk and node run — each
19
+ * with its parent among them and its kind, so a turn's
20
+ * span tree is stored (ADR 0139).
17
21
  * adk_payloads full request/response per model call, captured by
18
22
  * POLICY and expiring. ADK's own call_llm spans carry the
19
23
  * assembled prompt and raw response, and the native
@@ -41,7 +45,7 @@ import { join } from 'node:path';
41
45
  import core from '@opentelemetry/core';
42
46
  const { ExportResultCode } = core;
43
47
  import { hasSupabaseCredentials } from '../persistence/supabaseProvider.js';
44
- import { TELEMETRY_SCHEMA_VERSION, isModelCallSpan } from './lineage.js';
48
+ import { LEDGER_PARENT_ATTRIBUTE, TELEMETRY_SCHEMA_VERSION, isModelCallSpan, ledgerSpanKind } from './lineage.js';
45
49
  import { redactRow, telemetryRedactor } from './redact.js';
46
50
  import { outputsDir } from '../paths.js';
47
51
  /**
@@ -81,8 +85,17 @@ export function payloadDecision(policy, traceId, facts) {
81
85
  }
82
86
  // ── Span → row mapping (pure; tested offline) ───────────────────────────────
83
87
  const ROOT_PREFIX = 'Syndicate Execution: ';
84
- // A script's own spans (ADR 0137 item 16) carry its run, its calls' kinds, names, sequence and status, never an argument or a result.
85
- const EXPORTED_SPAN_NAMES = /^(llm\.request$|Syndicate Execution: |script\.(invoke|call) )/;
88
+ /**
89
+ * The spans that get an adk_telemetry row: every span ledgerSpanKind names
90
+ * (ADR 0139). A model step's row (`model.call`) leaves its payload to
91
+ * adk_payloads (spanJson drops `llm.payload.*`) and its tokens to its
92
+ * llm.request rows, so a trace's rows sum without counting a call twice. A
93
+ * script's own spans (ADR 0137 item 16) carry its run, its calls' kinds,
94
+ * names, sequence and status, never an argument or a result.
95
+ */
96
+ export function isLedgerSpan(span) {
97
+ return ledgerSpanKind(span.name) !== null;
98
+ }
86
99
  function attrsOf(span) {
87
100
  return span.attributes;
88
101
  }
@@ -169,6 +182,25 @@ function spanJson(span) {
169
182
  status: span.status,
170
183
  };
171
184
  }
185
+ /** The span's parent in the ledger: the ancestor the tracer recorded at start, else its direct parent. */
186
+ export function ledgerParentOf(span) {
187
+ const recorded = attrsOf(span)[LEDGER_PARENT_ATTRIBUTE];
188
+ if (typeof recorded === 'string' && recorded)
189
+ return recorded;
190
+ const s = span;
191
+ const parent = s.parentSpanContext?.spanId ?? s.parentSpanId;
192
+ return typeof parent === 'string' && parent ? parent : null;
193
+ }
194
+ /** Whether a span failed: its status, an error code it records, or a tool's error response. */
195
+ export function spanFailed(span) {
196
+ const attrs = attrsOf(span);
197
+ return (span.status?.code === 2 || // SpanStatusCode.ERROR
198
+ !!str(attrs, 'llm.error_code') ||
199
+ !!str(attrs, 'syndicate.error.code') ||
200
+ attrs['tool.error'] === true ||
201
+ attrs['script.call.status'] === 'error' ||
202
+ attrs['adk.node.status'] === 'failed');
203
+ }
172
204
  /** Span attributes minus the payload capture (`llm.payload.*`). */
173
205
  export function withoutPayloadAttributes(attrs) {
174
206
  const out = {};
@@ -177,7 +209,7 @@ export function withoutPayloadAttributes(attrs) {
177
209
  out[k] = v;
178
210
  return out;
179
211
  }
180
- /** Flatten an llm.request or root span into one adk_telemetry row. */
212
+ /** Flatten a span the ledger keeps (isLedgerSpan) into one adk_telemetry row. */
181
213
  export function toTelemetryRow(span) {
182
214
  const attrs = attrsOf(span);
183
215
  return {
@@ -192,11 +224,16 @@ export function toTelemetryRow(span) {
192
224
  input_tokens: num(attrs, 'llm.tokens.input') ?? num(attrs, 'syndicate.tokens.input'),
193
225
  output_tokens: num(attrs, 'llm.tokens.output') ?? num(attrs, 'syndicate.tokens.output'),
194
226
  thinking_tokens: num(attrs, 'llm.tokens.thinking') ?? num(attrs, 'syndicate.tokens.thinking'),
227
+ cache_read_tokens: num(attrs, 'llm.tokens.cache_read') ?? num(attrs, 'syndicate.tokens.cache_read'),
228
+ cache_write_tokens: num(attrs, 'llm.tokens.cache_write') ?? num(attrs, 'syndicate.tokens.cache_write'),
195
229
  latency_ms: durationMs(span),
196
230
  session_id: str(attrs, 'session.id'),
197
231
  user_id: str(attrs, 'user.id'),
198
232
  task_id: str(attrs, 'a2a.task_id'),
199
233
  invocation_id: str(attrs, 'adk.invocation_id'),
234
+ parent_span_id: ledgerParentOf(span),
235
+ kind: ledgerSpanKind(span.name),
236
+ error: spanFailed(span),
200
237
  schema_version: TELEMETRY_SCHEMA_VERSION,
201
238
  span: spanJson(span),
202
239
  };
@@ -241,6 +278,8 @@ export function toTurnRow(span) {
241
278
  input_tokens: num(attrs, 'syndicate.tokens.input'),
242
279
  output_tokens: num(attrs, 'syndicate.tokens.output'),
243
280
  thinking_tokens: num(attrs, 'syndicate.tokens.thinking'),
281
+ cache_read_tokens: num(attrs, 'syndicate.tokens.cache_read'),
282
+ cache_write_tokens: num(attrs, 'syndicate.tokens.cache_write'),
244
283
  latency_ms: durationMs(span),
245
284
  model_ms: num(attrs, 'syndicate.latency.model_ms'),
246
285
  tool_ms: num(attrs, 'syndicate.latency.tool_ms'),
@@ -371,7 +410,7 @@ export class SupabaseSpanExporter {
371
410
  const turnRows = [];
372
411
  const payloadRows = [];
373
412
  for (const span of spans) {
374
- if (EXPORTED_SPAN_NAMES.test(span.name))
413
+ if (isLedgerSpan(span))
375
414
  telemetryRows.push(toTelemetryRow(span));
376
415
  if (isRootSpan(span)) {
377
416
  const turn = toTurnRow(span);
@@ -2,7 +2,7 @@ import type { Context, Span, SpanContext, SpanOptions } from '@opentelemetry/api
2
2
  import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
3
3
  import type { LlmResponse } from '../models/genaiMapping.ts';
4
4
  import type { TurnEvent as Event } from '../runtime/events.ts';
5
- import type { ModelRequest } from '../models/contract.ts';
5
+ import type { ModelRequest, ModelResponse } from '../models/contract.ts';
6
6
  import type { ExporterOptions, LedgerClient } from './supabaseSpanExporter.ts';
7
7
  /**
8
8
  * Optional OTLP export for a live trace viewer (Phoenix, Langfuse, Grafana
@@ -45,6 +45,9 @@ interface TraceStats {
45
45
  toolMs: number;
46
46
  llmCalls: number;
47
47
  models: Set<string>;
48
+ /** Cache tokens summed over every call (`syndicate.tokens.cache_read` / `.cache_write`). */
49
+ cacheRead: number;
50
+ cacheWrite: number;
48
51
  /** Each model's calls and tokens, input summed per call (what a provider bills), for `syndicate.usage` (ADR 0136). */
49
52
  usage: Map<string, ModelUsage>;
50
53
  startedAt: number;
@@ -52,9 +55,13 @@ interface TraceStats {
52
55
  /** One model's share of a turn: what the ledger prices it from (model_pricing, ADR 0136). */
53
56
  export interface ModelUsage {
54
57
  calls: number;
58
+ /** Every input token, the cache's included (the contract's meaning, lib/models/contract.ts). */
55
59
  input: number;
56
60
  output: number;
57
61
  thinking: number;
62
+ /** The parts of `input` read from and written to the provider's prompt cache, priced at their own rates (ADR 0139). */
63
+ cache_read: number;
64
+ cache_write: number;
58
65
  }
59
66
  /** Read-and-forget the stats for a finished trace. */
60
67
  export declare function takeTraceStats(traceId: string): TraceStats | undefined;
@@ -171,6 +178,15 @@ export interface LlmCallMeta {
171
178
  * for an adapter written before the model contract.
172
179
  */
173
180
  llmRequest?: unknown;
181
+ /**
182
+ * The contract response a yielded LlmResponse was mapped from, when the
183
+ * caller holds it (the native step, the summarizer, the memory extractor).
184
+ * The span reads what an LlmResponse cannot carry from it: the cache
185
+ * write count and the contract's finish reason. Without it the span
186
+ * records the cache reads Gemini's usage names and no finish reason
187
+ * unless the call failed.
188
+ */
189
+ source?: (response: LlmResponse) => ModelResponse | undefined;
174
190
  }
175
191
  /**
176
192
  * Wraps one adapter `generateContentAsync` invocation in an `llm.request`
@@ -1,6 +1,6 @@
1
1
  import { trace, context, defaultTextMapGetter, ROOT_CONTEXT, INVALID_SPAN_CONTEXT } from '@opentelemetry/api';
2
2
  import { createRequire } from 'node:module';
3
- import { ADK_SPAN_SCOPE, RUNTIME_SPAN_SCOPE, TELEMETRY_SCHEMA_VERSION, agentOfSpanName, engineVersion, isModelCallSpan, isToolSpanName } from './lineage.js';
3
+ import { ADK_SPAN_SCOPE, LEDGER_PARENT_ATTRIBUTE, RUNTIME_SPAN_SCOPE, TELEMETRY_SCHEMA_VERSION, agentOfSpanName, engineVersion, isModelCallSpan, isToolSpanName, ledgerSpanKind } from './lineage.js';
4
4
  import { FilteringSpanExporter, otlpContentMode } from './otlpFilter.js';
5
5
  import { telemetryRedactor } from './redact.js';
6
6
  import { chargeLlmCall, chargeTokens, turnUntraced } from '../runtime/turnControl.js';
@@ -194,7 +194,7 @@ const TRACE_STATS_TTL_MS = 60 * 60 * 1000;
194
194
  function statsFor(traceId) {
195
195
  let s = traceStats.get(traceId);
196
196
  if (!s) {
197
- s = { modelMs: 0, toolMs: 0, llmCalls: 0, models: new Set(), usage: new Map(), startedAt: Date.now() };
197
+ s = { modelMs: 0, toolMs: 0, llmCalls: 0, models: new Set(), cacheRead: 0, cacheWrite: 0, usage: new Map(), startedAt: Date.now() };
198
198
  traceStats.set(traceId, s);
199
199
  if (traceStats.size > 2_000) {
200
200
  const cutoff = Date.now() - TRACE_STATS_TTL_MS;
@@ -219,6 +219,15 @@ class SpanLineageProcessor {
219
219
  const ctx = span.spanContext();
220
220
  const parent = span.parentSpanContext?.spanId ?? span.parentSpanId ?? undefined;
221
221
  spanLineage.set(ctx.spanId, { name: span.name, parent });
222
+ // The ledger's parent is the nearest ancestor it keeps a row for, read
223
+ // now: an ancestor's entry is gone once it ends, and the exporter sees
224
+ // the span only after that (ADR 0139). Recorded only when it differs
225
+ // from the direct parent, which the span carries itself.
226
+ if (ledgerSpanKind(span.name) !== null) {
227
+ const ledgerParent = ledgerAncestor(parent);
228
+ if (ledgerParent && ledgerParent !== parent)
229
+ span.setAttribute(LEDGER_PARENT_ATTRIBUTE, ledgerParent);
230
+ }
222
231
  // The model-call span (the loop's model.call, ADK's call_llm before 1.0.0: the one
223
232
  // carrying the full request/response payloads) learns its agent the same
224
233
  // way llm.request does, so a payload row can be attributed without
@@ -250,13 +259,21 @@ class SpanLineageProcessor {
250
259
  const v = span.attributes[key];
251
260
  return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : 0;
252
261
  };
253
- const u = s.usage.get(model) ?? { calls: 0, input: 0, output: 0, thinking: 0 };
262
+ const u = s.usage.get(model) ?? { calls: 0, input: 0, output: 0, thinking: 0, cache_read: 0, cache_write: 0 };
254
263
  u.calls += 1;
255
264
  u.input += tokens('llm.tokens.input');
256
265
  u.output += tokens('llm.tokens.output');
257
266
  u.thinking += tokens('llm.tokens.thinking');
267
+ u.cache_read += tokens('llm.tokens.cache_read');
268
+ u.cache_write += tokens('llm.tokens.cache_write');
258
269
  s.usage.set(model, u);
259
270
  }
271
+ const cache = (key) => {
272
+ const v = span.attributes[key];
273
+ return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : 0;
274
+ };
275
+ s.cacheRead += cache('llm.tokens.cache_read');
276
+ s.cacheWrite += cache('llm.tokens.cache_write');
260
277
  }
261
278
  else if (isToolSpanName(span.name)) {
262
279
  statsFor(traceId).toolMs += spanDurationMs(span);
@@ -265,6 +282,23 @@ class SpanLineageProcessor {
265
282
  shutdown() { return Promise.resolve(); }
266
283
  forceFlush() { return Promise.resolve(); }
267
284
  }
285
+ /**
286
+ * The nearest of `spanId` and its ancestors that the ledger keeps a row for
287
+ * (ledgerSpanKind), or `spanId` itself when the chain leaves the lineage
288
+ * (a remote parent, an evicted entry) or holds no such span.
289
+ */
290
+ function ledgerAncestor(spanId) {
291
+ let cursor = spanId;
292
+ for (let depth = 0; cursor && depth < 32; depth++) {
293
+ const entry = spanLineage.get(cursor);
294
+ if (!entry)
295
+ return depth === 0 ? spanId : cursor;
296
+ if (ledgerSpanKind(entry.name) !== null)
297
+ return cursor;
298
+ cursor = entry.parent;
299
+ }
300
+ return spanId;
301
+ }
268
302
  /** Name of the agent of the nearest enclosing agent span (ADK's `invoke_agent`, the loop's `agent.invoke`), or null. */
269
303
  export function agentForSpan(spanId) {
270
304
  let cursor = spanId;
@@ -557,6 +591,9 @@ export async function* traceAgentRun(stream, metadata) {
557
591
  span.setAttribute('syndicate.latency.tool_ms', Math.round(stats.toolMs));
558
592
  span.setAttribute('syndicate.llm_calls', stats.llmCalls);
559
593
  span.setAttribute('syndicate.models', [...stats.models].sort().join(','));
594
+ // Summed over every call, as a provider bills them (adk_turns.cache_read_tokens / cache_write_tokens).
595
+ span.setAttribute('syndicate.tokens.cache_read', stats.cacheRead);
596
+ span.setAttribute('syndicate.tokens.cache_write', stats.cacheWrite);
560
597
  // The ledger prices the turn from this, per model (melchizedek_usage_cost, ADR 0136).
561
598
  span.setAttribute('syndicate.usage', JSON.stringify(Object.fromEntries([...stats.usage].sort(([a], [b]) => a.localeCompare(b)))));
562
599
  }
@@ -673,6 +710,9 @@ export async function* traceLlmGeneration(meta, inner) {
673
710
  let inputTokens = 0;
674
711
  let outputTokens = 0;
675
712
  let thinkingTokens = 0;
713
+ let cacheRead;
714
+ let cacheWrite;
715
+ let finishReason;
676
716
  let thinkingPreview = '';
677
717
  let errorCode = '';
678
718
  let errorMessage = '';
@@ -689,6 +729,15 @@ export async function* traceLlmGeneration(meta, inner) {
689
729
  inputTokens = resp.usageMetadata.promptTokenCount ?? inputTokens;
690
730
  outputTokens = resp.usageMetadata.candidatesTokenCount ?? outputTokens;
691
731
  thinkingTokens = resp.usageMetadata.thoughtsTokenCount ?? thinkingTokens;
732
+ cacheRead = resp.usageMetadata.cachedContentTokenCount ?? cacheRead;
733
+ }
734
+ const source = meta.source?.(resp);
735
+ if (source && !source.partial) {
736
+ finishReason = source.finishReason;
737
+ if (source.usage?.cacheReadTokens !== undefined)
738
+ cacheRead = source.usage.cacheReadTokens;
739
+ if (source.usage?.cacheWriteTokens !== undefined)
740
+ cacheWrite = source.usage.cacheWriteTokens;
692
741
  }
693
742
  if (resp.errorCode) {
694
743
  errorCode = String(resp.errorCode);
@@ -733,6 +782,15 @@ export async function* traceLlmGeneration(meta, inner) {
733
782
  span.setAttribute('llm.tokens.input', inputTokens);
734
783
  span.setAttribute('llm.tokens.output', outputTokens);
735
784
  span.setAttribute('llm.tokens.thinking', thinkingTokens);
785
+ // Parts of llm.tokens.input, set only when the provider reported them (ADR 0139).
786
+ if (cacheRead !== undefined)
787
+ span.setAttribute('llm.tokens.cache_read', cacheRead);
788
+ if (cacheWrite !== undefined)
789
+ span.setAttribute('llm.tokens.cache_write', cacheWrite);
790
+ if (errorCode)
791
+ finishReason = 'error';
792
+ if (finishReason)
793
+ span.setAttribute('llm.finish_reason', finishReason);
736
794
  span.setAttribute('gen_ai.usage.input_tokens', inputTokens);
737
795
  span.setAttribute('gen_ai.usage.output_tokens', outputTokens);
738
796
  // llm.latency_ms mirrors the span duration for the flat adk_telemetry row.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * lib/project.ts — the project manifest, `melchizedek.json` (ADR 0138).
3
+ *
4
+ * A project is a folder, usually a git repository, with a `melchizedek.json`
5
+ * at its root. The manifest records what the engine cannot infer from the
6
+ * files: which folder holds the syndicates, which engine version the project
7
+ * is written for, and how each file ships. It holds names, never values: an
8
+ * `env` entry is a variable name, and no field may carry a secret.
9
+ *
10
+ * `findProject()` walks up from a directory to the nearest manifest and stops
11
+ * at the repository root (the first directory holding `.git`) or the home
12
+ * directory, so a manifest outside the repository never steers it.
13
+ * `resolveAgentsDir()` is the one agents-directory chain every loader and bin
14
+ * uses: explicit option → MELCHIZEDEK_AGENTS_DIR → the manifest's `agents` →
15
+ * <cwd>/config/agents. A deployed server sets the variable, so the manifest
16
+ * only changes what a bare command run inside a project finds.
17
+ *
18
+ * Imports only node built-ins, zod and lib/paths.ts.
19
+ */
20
+ import { z } from 'zod';
21
+ export declare const PROJECT_MANIFEST = "melchizedek.json";
22
+ export declare const MANIFEST_VERSION = 1;
23
+ export declare const projectManifestSchema: z.ZodObject<{
24
+ manifest: z.ZodNumber;
25
+ engine: z.ZodString;
26
+ agents: z.ZodString;
27
+ ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ tests: z.ZodOptional<z.ZodString>;
29
+ env: z.ZodOptional<z.ZodArray<z.ZodString>>;
30
+ syndicates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
31
+ deliverable: z.ZodOptional<z.ZodEnum<{
32
+ library: "library";
33
+ mcp: "mcp";
34
+ service: "service";
35
+ }>>;
36
+ publish: z.ZodOptional<z.ZodObject<{
37
+ kind: z.ZodLiteral<"registry">;
38
+ id: z.ZodString;
39
+ }, z.core.$loose>>;
40
+ }, z.core.$loose>>>;
41
+ registry: z.ZodOptional<z.ZodObject<{
42
+ kind: z.ZodLiteral<"supabase">;
43
+ url_env: z.ZodOptional<z.ZodString>;
44
+ restart: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$loose>>;
46
+ production: z.ZodOptional<z.ZodObject<{
47
+ ledger: z.ZodOptional<z.ZodObject<{
48
+ kind: z.ZodLiteral<"postgres">;
49
+ url_env: z.ZodString;
50
+ }, z.core.$loose>>;
51
+ }, z.core.$loose>>;
52
+ tools: z.ZodOptional<z.ZodObject<{
53
+ remote: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
54
+ mcp_env: z.ZodString;
55
+ }, z.core.$loose>>>;
56
+ }, z.core.$loose>>;
57
+ }, z.core.$loose>;
58
+ export type ProjectManifest = z.infer<typeof projectManifestSchema>;
59
+ export interface Project {
60
+ /** The folder holding `melchizedek.json`. */
61
+ root: string;
62
+ /** Absolute path of the manifest. */
63
+ file: string;
64
+ manifest: ProjectManifest;
65
+ /** `agents`, resolved under the root. */
66
+ agentsDir: string;
67
+ /** `tests`, resolved under the root (default `tests/agents`). */
68
+ testsDir: string;
69
+ }
70
+ /** Thrown for a manifest that does not parse or validate; names the file and fields, never a value. */
71
+ export declare class ProjectManifestError extends Error {
72
+ readonly file: string;
73
+ readonly issues: string[];
74
+ constructor(file: string, issues: string[]);
75
+ }
76
+ /**
77
+ * The nearest folder at or above `from` holding a manifest, without crossing
78
+ * the repository root or the home directory. Undefined when there is none.
79
+ */
80
+ export declare function findProjectRoot(from?: string): string | undefined;
81
+ /** Parse and validate a manifest's text. Throws `ProjectManifestError`. */
82
+ export declare function parseProjectManifest(text: string, file?: string): ProjectManifest;
83
+ /** Load the project rooted at `root` (the folder holding the manifest). Throws `ProjectManifestError`. */
84
+ export declare function loadProject(root: string): Project;
85
+ /** The project at or above `from`, or undefined when there is none. Throws on a broken manifest. */
86
+ export declare function findProject(from?: string): Project | undefined;
87
+ /** Forget cached manifest lookups (tests, and an embedder that edits the manifest in-process). */
88
+ export declare function clearProjectCache(): void;
89
+ /**
90
+ * The agents directory: `explicit` → MELCHIZEDEK_AGENTS_DIR → the nearest
91
+ * manifest's `agents` → <cwd>/config/agents. Always absolute.
92
+ */
93
+ export declare function resolveAgentsDir(explicit?: string, env?: NodeJS.ProcessEnv): string;
94
+ /** True when `relFromRoot` (a `/`-separated path relative to the project root) matches an `ignore` pattern. */
95
+ export declare function isIgnored(project: Pick<Project, 'manifest'>, relFromRoot: string): boolean;
96
+ /** The syndicate files directly in the agents folder, as names relative to it, minus `ignore`. */
97
+ export declare function listProjectSyndicates(project: Project): string[];
98
+ /** The installed engine's package version (no commit suffix). */
99
+ export declare function enginePackageVersion(): string;
100
+ export interface ProjectCheck {
101
+ root: string;
102
+ agentsDir: string;
103
+ /** The manifest's engine pin, and the engine running now. */
104
+ engine: {
105
+ pinned: string;
106
+ running: string;
107
+ matches: boolean;
108
+ };
109
+ syndicates: string[];
110
+ /** Stop `melchizedek-doctor --check`. */
111
+ problems: string[];
112
+ /** Shown, never fatal. */
113
+ warnings: string[];
114
+ }
115
+ /** What is wrong with a project: engine pin, missing folders, stale ignores, unset env names. Never reads a value. */
116
+ export declare function checkProject(project: Project, running?: string, env?: NodeJS.ProcessEnv): ProjectCheck;