@flyingrobots/graft 0.3.5 → 0.5.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 (111) hide show
  1. package/ARCHITECTURE.md +386 -0
  2. package/CHANGELOG.md +69 -0
  3. package/CODE_OF_CONDUCT.md +65 -0
  4. package/README.md +153 -17
  5. package/bin/graft.js +4 -11
  6. package/docs/ADVANCED_GUIDE.md +49 -0
  7. package/docs/CLI.md +43 -0
  8. package/docs/GUIDE.md +321 -32
  9. package/docs/MCP.md +44 -0
  10. package/package.json +17 -4
  11. package/src/adapters/node-fs.ts +4 -0
  12. package/src/adapters/node-git.ts +47 -0
  13. package/src/adapters/node-process-runner.ts +27 -0
  14. package/src/cli/index-cmd.ts +86 -0
  15. package/src/cli/init.ts +808 -57
  16. package/src/cli/main.ts +437 -0
  17. package/src/contracts/capabilities.ts +341 -0
  18. package/src/contracts/causal-ontology.ts +622 -0
  19. package/src/contracts/causal-surface-next-action.ts +18 -0
  20. package/src/contracts/output-schemas.ts +1169 -0
  21. package/src/git/diff.ts +25 -21
  22. package/src/git/target-git-hook-bootstrap.ts +56 -0
  23. package/src/hooks/posttooluse-read.ts +21 -74
  24. package/src/hooks/pretooluse-read.ts +20 -56
  25. package/src/hooks/read-governor.ts +95 -0
  26. package/src/hooks/read-messages.ts +53 -0
  27. package/src/mcp/burden.ts +123 -0
  28. package/src/mcp/cache.ts +51 -0
  29. package/src/mcp/cached-file.ts +10 -8
  30. package/src/mcp/context.ts +67 -2
  31. package/src/mcp/daemon-control-plane.ts +554 -0
  32. package/src/mcp/daemon-job-scheduler.ts +279 -0
  33. package/src/mcp/daemon-repos.ts +216 -0
  34. package/src/mcp/daemon-server.ts +396 -0
  35. package/src/mcp/daemon-worker-pool.ts +310 -0
  36. package/src/mcp/daemon-worker-process.ts +52 -0
  37. package/src/mcp/metrics.ts +108 -1
  38. package/src/mcp/monitor-tick-job.ts +99 -0
  39. package/src/mcp/persisted-local-history.ts +1246 -0
  40. package/src/mcp/persistent-monitor-runtime.ts +549 -0
  41. package/src/mcp/policy.ts +84 -0
  42. package/src/mcp/receipt.ts +82 -12
  43. package/src/mcp/repo-concurrency.ts +318 -0
  44. package/src/mcp/repo-state.ts +777 -0
  45. package/src/mcp/repo-tool-job.ts +302 -0
  46. package/src/mcp/run-capture-config.ts +33 -0
  47. package/src/mcp/runtime-causal-context.ts +72 -0
  48. package/src/mcp/runtime-observability.ts +219 -0
  49. package/src/mcp/runtime-staged-target.ts +161 -0
  50. package/src/mcp/runtime-workspace-overlay.ts +255 -0
  51. package/src/mcp/semantic-transition-guidance.ts +60 -0
  52. package/src/mcp/semantic-transition-summary.ts +130 -0
  53. package/src/mcp/server.ts +704 -45
  54. package/src/mcp/stdio-server.ts +12 -0
  55. package/src/mcp/stdio.ts +2 -5
  56. package/src/mcp/tools/activity-view.ts +325 -0
  57. package/src/mcp/tools/causal-attach.ts +67 -0
  58. package/src/mcp/tools/causal-status.ts +58 -0
  59. package/src/mcp/tools/changed-since.ts +13 -11
  60. package/src/mcp/tools/code-find.ts +164 -0
  61. package/src/mcp/tools/code-refs.ts +466 -0
  62. package/src/mcp/tools/code-show.ts +252 -0
  63. package/src/mcp/tools/daemon-monitors.ts +14 -0
  64. package/src/mcp/tools/daemon-repos.ts +22 -0
  65. package/src/mcp/tools/daemon-sessions.ts +14 -0
  66. package/src/mcp/tools/daemon-status.ts +12 -0
  67. package/src/mcp/tools/doctor.ts +45 -2
  68. package/src/mcp/tools/explain.ts +4 -0
  69. package/src/mcp/tools/file-outline.ts +7 -3
  70. package/src/mcp/tools/git-files.ts +73 -0
  71. package/src/mcp/tools/graft-diff.ts +12 -4
  72. package/src/mcp/tools/map.ts +136 -0
  73. package/src/mcp/tools/monitor-pause.ts +18 -0
  74. package/src/mcp/tools/monitor-resume.ts +18 -0
  75. package/src/mcp/tools/monitor-start.ts +20 -0
  76. package/src/mcp/tools/monitor-stop.ts +18 -0
  77. package/src/mcp/tools/precision-match.ts +51 -0
  78. package/src/mcp/tools/precision-query.ts +127 -0
  79. package/src/mcp/tools/precision.ts +312 -0
  80. package/src/mcp/tools/run-capture.ts +126 -44
  81. package/src/mcp/tools/safe-read.ts +14 -12
  82. package/src/mcp/tools/since.ts +49 -0
  83. package/src/mcp/tools/state.ts +11 -3
  84. package/src/mcp/tools/stats.ts +5 -1
  85. package/src/mcp/tools/workspace-authorizations.ts +14 -0
  86. package/src/mcp/tools/workspace-authorize.ts +20 -0
  87. package/src/mcp/tools/workspace-bind.ts +25 -0
  88. package/src/mcp/tools/workspace-rebind.ts +25 -0
  89. package/src/mcp/tools/workspace-revoke.ts +18 -0
  90. package/src/mcp/tools/workspace-status.ts +12 -0
  91. package/src/mcp/warp-pool.ts +36 -0
  92. package/src/mcp/workspace-router.ts +984 -0
  93. package/src/operations/file-outline.ts +12 -2
  94. package/src/operations/graft-diff.ts +56 -10
  95. package/src/operations/safe-read.ts +27 -4
  96. package/src/operations/state.ts +6 -9
  97. package/src/parser/lang.ts +19 -3
  98. package/src/parser/outline.ts +191 -2
  99. package/src/parser/types.ts +9 -1
  100. package/src/policy/types.ts +4 -3
  101. package/src/ports/filesystem.ts +1 -0
  102. package/src/ports/git.ts +16 -0
  103. package/src/ports/process-runner.ts +22 -0
  104. package/src/release/security-gate.ts +102 -0
  105. package/src/session/tracker.ts +31 -0
  106. package/src/version.ts +3 -0
  107. package/src/warp/indexer.ts +513 -0
  108. package/src/warp/observers.ts +105 -0
  109. package/src/warp/open.ts +31 -0
  110. package/src/warp/plumbing.d.ts +15 -0
  111. package/src/warp/writer-id.ts +30 -0
@@ -0,0 +1,1169 @@
1
+ import { z } from "zod";
2
+ import {
3
+ CLI_COMMAND_NAMES,
4
+ type CliCommandName,
5
+ CLI_COMMAND_TO_MCP_TOOL,
6
+ MCP_TOOL_NAMES,
7
+ type McpToolName,
8
+ } from "./capabilities.js";
9
+ import {
10
+ attributionSummarySchema,
11
+ attributionConfidenceSchema,
12
+ evidenceSchema,
13
+ readEventSchema,
14
+ localHistoryContinuityOperationSchema,
15
+ repoConcurrencyAuthoritySchema,
16
+ repoConcurrencyPostureSchema,
17
+ stageEventSchema,
18
+ transitionEventSchema,
19
+ stagedTargetSchema,
20
+ } from "./causal-ontology.js";
21
+ import { causalSurfaceNextActionSchema } from "./causal-surface-next-action.js";
22
+
23
+ export { CLI_COMMAND_NAMES, MCP_TOOL_NAMES };
24
+ export type { CliCommandName, McpToolName } from "./capabilities.js";
25
+
26
+ export const OUTPUT_SCHEMA_VERSION = "1.0.0" as const;
27
+
28
+ export interface OutputSchemaMeta {
29
+ readonly id: string;
30
+ readonly version: typeof OUTPUT_SCHEMA_VERSION;
31
+ }
32
+
33
+ const mcpOutputSchemaMeta = Object.freeze(Object.fromEntries(
34
+ MCP_TOOL_NAMES.map((tool) => [tool, Object.freeze({
35
+ id: `graft.mcp.${tool}`,
36
+ version: OUTPUT_SCHEMA_VERSION,
37
+ })]),
38
+ ) as Record<McpToolName, OutputSchemaMeta>);
39
+
40
+ const cliOutputSchemaMeta = Object.freeze(Object.fromEntries(
41
+ CLI_COMMAND_NAMES.map((command) => [command, Object.freeze({
42
+ id: `graft.cli.${command}`,
43
+ version: OUTPUT_SCHEMA_VERSION,
44
+ })]),
45
+ ) as Record<CliCommandName, OutputSchemaMeta>);
46
+
47
+ function schemaMetaLiteral(meta: OutputSchemaMeta) {
48
+ return z.object({
49
+ id: z.literal(meta.id),
50
+ version: z.literal(meta.version),
51
+ }).strict();
52
+ }
53
+
54
+ const sessionDepthSchema = z.enum(["early", "mid", "late", "unknown"]);
55
+ const worldlineLayerSchema = z.enum(["commit_worldline", "ref_view", "workspace_overlay"]);
56
+ const repoTransitionKindSchema = z.enum(["checkout", "reset", "merge", "rebase"]);
57
+
58
+ const actualSchema = z.object({
59
+ lines: z.number().int().nonnegative(),
60
+ bytes: z.number().int().nonnegative(),
61
+ }).strict();
62
+
63
+ const thresholdsSchema = z.object({
64
+ lines: z.number().int().nonnegative(),
65
+ bytes: z.number().int().nonnegative(),
66
+ }).strict();
67
+
68
+ const budgetSchema = z.object({
69
+ total: z.number().int().positive(),
70
+ consumed: z.number().int().nonnegative(),
71
+ remaining: z.number().int().nonnegative(),
72
+ fraction: z.number(),
73
+ }).strict();
74
+
75
+ const tripwireSchema = z.object({
76
+ _brand: z.literal("Tripwire").optional(),
77
+ signal: z.string(),
78
+ recommendation: z.string(),
79
+ }).strict();
80
+
81
+ const outlineEntrySchema: z.ZodType = z.lazy(() => z.object({
82
+ _brand: z.literal("OutlineEntry").optional(),
83
+ kind: z.string(),
84
+ name: z.string(),
85
+ signature: z.string().optional(),
86
+ exported: z.boolean(),
87
+ children: z.array(outlineEntrySchema).optional(),
88
+ }).strict());
89
+
90
+ const jumpEntrySchema = z.object({
91
+ _brand: z.literal("JumpEntry").optional(),
92
+ symbol: z.string(),
93
+ kind: z.string(),
94
+ start: z.number().int().positive(),
95
+ end: z.number().int().positive(),
96
+ }).strict();
97
+
98
+ const outlineDiffSchema: z.ZodType = z.lazy(() => z.object({
99
+ _brand: z.literal("OutlineDiff").optional(),
100
+ added: z.array(diffEntrySchema),
101
+ removed: z.array(diffEntrySchema),
102
+ changed: z.array(diffEntrySchema),
103
+ unchangedCount: z.number().int().nonnegative(),
104
+ }).strict());
105
+
106
+ const diffEntrySchema: z.ZodType = z.lazy(() => z.object({
107
+ _brand: z.literal("DiffEntry").optional(),
108
+ name: z.string(),
109
+ kind: z.string(),
110
+ signature: z.string().optional(),
111
+ oldSignature: z.string().optional(),
112
+ childDiff: outlineDiffSchema.optional(),
113
+ }).strict());
114
+
115
+ const burdenKindSchema = z.enum(["read", "search", "shell", "state", "diagnostic"]);
116
+
117
+ const burdenBucketSchema = z.object({
118
+ calls: z.number().int().nonnegative(),
119
+ bytesReturned: z.number().int().nonnegative(),
120
+ }).strict();
121
+
122
+ const burdenByKindSchema = z.object({
123
+ read: burdenBucketSchema,
124
+ search: burdenBucketSchema,
125
+ shell: burdenBucketSchema,
126
+ state: burdenBucketSchema,
127
+ diagnostic: burdenBucketSchema,
128
+ }).strict();
129
+
130
+ const receiptSchema = z.object({
131
+ sessionId: z.string(),
132
+ traceId: z.string(),
133
+ seq: z.number().int().positive(),
134
+ ts: z.string(),
135
+ tool: z.string(),
136
+ projection: z.string(),
137
+ reason: z.string(),
138
+ latencyMs: z.number().int().nonnegative(),
139
+ fileBytes: z.number().int().nonnegative().nullable(),
140
+ returnedBytes: z.number().int().nonnegative(),
141
+ burden: z.object({
142
+ kind: burdenKindSchema,
143
+ nonRead: z.boolean(),
144
+ }).strict(),
145
+ cumulative: z.object({
146
+ reads: z.number().int().nonnegative(),
147
+ outlines: z.number().int().nonnegative(),
148
+ refusals: z.number().int().nonnegative(),
149
+ cacheHits: z.number().int().nonnegative(),
150
+ bytesReturned: z.number().int().nonnegative(),
151
+ bytesAvoided: z.number().int().nonnegative(),
152
+ nonReadBytesReturned: z.number().int().nonnegative(),
153
+ burdenByKind: burdenByKindSchema,
154
+ }).strict(),
155
+ budget: budgetSchema.optional(),
156
+ compressionRatio: z.number().nullable().optional(),
157
+ }).strict();
158
+
159
+ const runtimeObservabilitySchema = z.object({
160
+ enabled: z.boolean(),
161
+ logPath: z.string(),
162
+ maxBytes: z.number().int().positive(),
163
+ logPolicy: z.literal("metadata_only"),
164
+ }).strict();
165
+
166
+ const runtimeCausalContextSchema = z.object({
167
+ transportSessionId: z.string(),
168
+ workspaceSliceId: z.string(),
169
+ causalSessionId: z.string(),
170
+ strandId: z.string(),
171
+ checkoutEpochId: z.string(),
172
+ warpWriterId: z.string(),
173
+ stability: z.literal("runtime_local"),
174
+ provenanceLevel: z.literal("artifact_history"),
175
+ }).strict();
176
+
177
+ const runtimeLocalProvenanceSchema = z.object({
178
+ stability: z.literal("runtime_local"),
179
+ provenanceLevel: z.literal("artifact_history"),
180
+ }).strict();
181
+
182
+ const runtimeStagedTargetSchema = z.discriminatedUnion("availability", [
183
+ runtimeLocalProvenanceSchema.extend({
184
+ availability: z.literal("none"),
185
+ }).strict(),
186
+ runtimeLocalProvenanceSchema.extend({
187
+ availability: z.literal("full_file"),
188
+ attribution: attributionSummarySchema,
189
+ target: stagedTargetSchema.safeExtend({
190
+ selectionKind: z.literal("full_file"),
191
+ }),
192
+ }).strict(),
193
+ runtimeLocalProvenanceSchema.extend({
194
+ availability: z.literal("ambiguous"),
195
+ attribution: attributionSummarySchema,
196
+ reason: z.enum([
197
+ "missing_head_commit",
198
+ "missing_workspace_overlay",
199
+ "modified_path_selection_requires_deeper_evidence",
200
+ ]),
201
+ observedStagedPaths: z.number().int().positive(),
202
+ ambiguousPaths: z.array(z.string()).min(1),
203
+ }).strict(),
204
+ ]);
205
+
206
+ const activityViewAnchorSchema = z.discriminatedUnion("posture", [
207
+ z.object({
208
+ posture: z.literal("head_commit"),
209
+ headRef: z.string().nullable(),
210
+ headSha: z.string(),
211
+ }).strict(),
212
+ z.object({
213
+ posture: z.literal("unknown"),
214
+ headRef: z.string().nullable(),
215
+ headSha: z.string().nullable(),
216
+ reason: z.enum(["workspace_unbound", "missing_head_commit"]),
217
+ }).strict(),
218
+ ]);
219
+
220
+ const activityViewContinuityItemSchema = z.object({
221
+ itemKind: z.literal("continuity"),
222
+ recordId: z.string(),
223
+ operation: localHistoryContinuityOperationSchema,
224
+ occurredAt: z.string(),
225
+ causalSessionId: z.string(),
226
+ strandId: z.string(),
227
+ attribution: attributionSummarySchema,
228
+ continuedFromCausalSessionId: z.string().nullable(),
229
+ continuedFromStrandId: z.string().nullable(),
230
+ }).strict();
231
+
232
+ const activityViewItemSchema = z.union([
233
+ activityViewContinuityItemSchema,
234
+ readEventSchema,
235
+ stageEventSchema,
236
+ transitionEventSchema,
237
+ ]);
238
+
239
+ const activityViewGroupSchema = z.object({
240
+ groupKind: z.enum(["transition", "stage", "continuity", "read"]),
241
+ label: z.string(),
242
+ summary: z.string(),
243
+ count: z.number().int().positive(),
244
+ items: z.array(activityViewItemSchema),
245
+ }).strict();
246
+
247
+ const activityViewSummarySchema = z.object({
248
+ headline: z.string(),
249
+ anchor: z.string(),
250
+ workspace: z.string(),
251
+ groups: z.array(z.string()),
252
+ }).strict();
253
+
254
+ const persistedLocalHistorySummarySchema = z.discriminatedUnion("availability", [
255
+ z.object({
256
+ availability: z.literal("none"),
257
+ persistence: z.literal("persisted_local_history"),
258
+ historyPath: z.string().nullable(),
259
+ totalContinuityRecords: z.literal(0),
260
+ active: z.literal(false),
261
+ lastOperation: z.null(),
262
+ lastObservedAt: z.null(),
263
+ continuityKey: z.null(),
264
+ causalSessionId: z.null(),
265
+ strandId: z.null(),
266
+ checkoutEpochId: z.null(),
267
+ continuedFromCausalSessionId: z.null(),
268
+ continuityConfidence: attributionConfidenceSchema,
269
+ continuityEvidence: z.array(evidenceSchema),
270
+ attribution: attributionSummarySchema,
271
+ latestReadEvent: z.null(),
272
+ latestStageEvent: z.null(),
273
+ latestTransitionEvent: z.null(),
274
+ preserves: z.array(z.string()),
275
+ excludes: z.array(z.string()),
276
+ nextAction: z.literal("bind_workspace_to_begin_local_history"),
277
+ }).strict(),
278
+ z.object({
279
+ availability: z.literal("present"),
280
+ persistence: z.literal("persisted_local_history"),
281
+ historyPath: z.string(),
282
+ totalContinuityRecords: z.number().int().positive(),
283
+ active: z.boolean(),
284
+ lastOperation: localHistoryContinuityOperationSchema,
285
+ lastObservedAt: z.string(),
286
+ continuityKey: z.string(),
287
+ causalSessionId: z.string(),
288
+ strandId: z.string(),
289
+ checkoutEpochId: z.string(),
290
+ continuedFromCausalSessionId: z.string().nullable(),
291
+ continuityConfidence: attributionConfidenceSchema,
292
+ continuityEvidence: z.array(evidenceSchema),
293
+ attribution: attributionSummarySchema,
294
+ latestReadEvent: readEventSchema.nullable(),
295
+ latestStageEvent: stageEventSchema.nullable(),
296
+ latestTransitionEvent: transitionEventSchema.nullable(),
297
+ preserves: z.array(z.string()),
298
+ excludes: z.array(z.string()),
299
+ nextAction: z.enum([
300
+ "continue_active_causal_workspace",
301
+ "review_transition_boundary_before_continuing",
302
+ "inspect_or_resume_local_history",
303
+ ]),
304
+ }).strict(),
305
+ ]);
306
+
307
+ const repoConcurrencySummarySchema = z.object({
308
+ posture: repoConcurrencyPostureSchema,
309
+ authority: repoConcurrencyAuthoritySchema,
310
+ observedWorktreeCount: z.number().int().positive(),
311
+ observedCausalSessionCount: z.number().int().nonnegative(),
312
+ observedActorCount: z.number().int().nonnegative(),
313
+ overlappingPathCount: z.number().int().nonnegative(),
314
+ summary: z.string().min(1),
315
+ }).strict();
316
+
317
+ const precisionSymbolMatchSchema = z.object({
318
+ name: z.string(),
319
+ kind: z.string(),
320
+ path: z.string(),
321
+ signature: z.string().optional(),
322
+ exported: z.boolean(),
323
+ startLine: z.number().int().positive().optional(),
324
+ endLine: z.number().int().positive().optional(),
325
+ }).strict();
326
+
327
+ const codeRefsMatchSchema = z.object({
328
+ path: z.string(),
329
+ line: z.number().int().positive(),
330
+ column: z.number().int().positive().optional(),
331
+ preview: z.string(),
332
+ }).strict();
333
+
334
+ const codeRefsProvenanceSchema = z.object({
335
+ engine: z.enum(["ripgrep", "grep"]),
336
+ pattern: z.string(),
337
+ approximate: z.literal(true),
338
+ filesSearched: z.number().int().nonnegative(),
339
+ }).strict();
340
+
341
+ const structuralRefusalSchema = z.object({
342
+ path: z.string(),
343
+ reason: z.string(),
344
+ reasonDetail: z.string(),
345
+ next: z.array(z.string()),
346
+ actual: actualSchema,
347
+ }).strict();
348
+
349
+ const mapFileSchema = z.object({
350
+ path: z.string(),
351
+ lang: z.string(),
352
+ symbols: z.array(z.object({
353
+ name: z.string(),
354
+ kind: z.string(),
355
+ signature: z.string().optional(),
356
+ exported: z.boolean(),
357
+ startLine: z.number().int().positive().optional(),
358
+ endLine: z.number().int().positive().optional(),
359
+ }).strict()),
360
+ }).strict();
361
+
362
+ const fileDiffSchema = z.object({
363
+ path: z.string(),
364
+ status: z.enum(["modified", "added", "deleted"]),
365
+ summary: z.string(),
366
+ diff: outlineDiffSchema,
367
+ }).strict();
368
+
369
+ const repoTransitionSchema = z.object({
370
+ kind: repoTransitionKindSchema,
371
+ fromRef: z.string().nullable(),
372
+ toRef: z.string().nullable(),
373
+ fromCommit: z.string().nullable(),
374
+ toCommit: z.string().nullable(),
375
+ evidence: z.object({
376
+ reflogSubject: z.string().nullable(),
377
+ }).strict(),
378
+ }).strict();
379
+
380
+ const repoSemanticTransitionSchema = z.object({
381
+ kind: z.enum([
382
+ "index_update",
383
+ "conflict_resolution",
384
+ "merge_phase",
385
+ "rebase_phase",
386
+ "bulk_transition",
387
+ "unknown",
388
+ ]),
389
+ authority: z.enum([
390
+ "authoritative_git_state",
391
+ "repo_snapshot",
392
+ ]),
393
+ phase: z.enum([
394
+ "started",
395
+ "conflicted",
396
+ "resolved_waiting_commit",
397
+ "continued",
398
+ "completed_or_cleared",
399
+ ]).nullable(),
400
+ summary: z.string(),
401
+ evidence: z.object({
402
+ totalPaths: z.number().int().nonnegative(),
403
+ stagedPaths: z.number().int().nonnegative(),
404
+ changedPaths: z.number().int().nonnegative(),
405
+ untrackedPaths: z.number().int().nonnegative(),
406
+ unmergedPaths: z.number().int().nonnegative(),
407
+ mergeInProgress: z.boolean(),
408
+ rebaseInProgress: z.boolean(),
409
+ rebaseStep: z.number().int().positive().nullable(),
410
+ rebaseTotalSteps: z.number().int().positive().nullable(),
411
+ lastTransitionKind: repoTransitionKindSchema.nullable(),
412
+ reflogSubject: z.string().nullable(),
413
+ }).strict(),
414
+ }).strict();
415
+
416
+ const workspaceOverlaySummarySchema = z.object({
417
+ dirty: z.literal(true),
418
+ totalPaths: z.number().int().nonnegative(),
419
+ stagedPaths: z.number().int().nonnegative(),
420
+ changedPaths: z.number().int().nonnegative(),
421
+ untrackedPaths: z.number().int().nonnegative(),
422
+ actorGuess: z.literal("unknown"),
423
+ confidence: z.literal("low"),
424
+ evidence: z.object({
425
+ source: z.literal("git status --porcelain"),
426
+ reflogSubject: z.string().nullable(),
427
+ sample: z.array(z.string()),
428
+ }).strict(),
429
+ }).strict();
430
+
431
+ const gitHookBootstrapStatusSchema = z.object({
432
+ posture: z.enum(["absent", "external_unknown", "installed"]),
433
+ configuredCoreHooksPath: z.string().nullable(),
434
+ resolvedHooksPath: z.string(),
435
+ requiredHooks: z.array(z.string()),
436
+ presentHooks: z.array(z.string()),
437
+ missingHooks: z.array(z.string()),
438
+ supportsCheckoutBoundaries: z.boolean(),
439
+ }).strict();
440
+
441
+ const gitTransitionHookEventSchema = z.object({
442
+ hookName: z.enum(["post-checkout", "post-merge", "post-rewrite"]),
443
+ hookArgs: z.array(z.string()),
444
+ worktreeRoot: z.string(),
445
+ observedAt: z.string(),
446
+ }).strict();
447
+
448
+ const workspaceOverlayFootingSchema = z.object({
449
+ observationMode: z.enum([
450
+ "inferred_between_tool_calls",
451
+ "hook_observed_checkout_boundaries",
452
+ ]),
453
+ lineagePosture: z.enum([
454
+ "stable",
455
+ "forked_after_transition",
456
+ ]),
457
+ boundaryAuthority: z.enum([
458
+ "none",
459
+ "repo_snapshot",
460
+ "hook_observed",
461
+ ]),
462
+ degraded: z.literal(true),
463
+ degradedReason: z.enum([
464
+ "target_repo_hooks_absent",
465
+ "target_repo_hooks_unrecognized",
466
+ "local_edit_watchers_absent",
467
+ ]),
468
+ checkoutEpoch: z.number().int().nonnegative(),
469
+ lastTransition: repoTransitionSchema.nullable(),
470
+ workspaceOverlayId: z.string().nullable(),
471
+ workspaceOverlay: workspaceOverlaySummarySchema.nullable(),
472
+ hookBootstrap: gitHookBootstrapStatusSchema,
473
+ latestHookEvent: gitTransitionHookEventSchema.nullable(),
474
+ }).strict();
475
+
476
+ const policyBoundarySchema = z.object({
477
+ kind: z.literal("shell_escape_hatch"),
478
+ boundedReadContract: z.literal(false),
479
+ policyEnforced: z.literal(false),
480
+ }).strict();
481
+
482
+ const burdenSummarySchema = z.object({
483
+ totalBytesReturned: z.number().int().nonnegative(),
484
+ totalNonReadBytesReturned: z.number().int().nonnegative(),
485
+ topKind: burdenKindSchema.nullable(),
486
+ topBytesReturned: z.number().int().nonnegative(),
487
+ topCalls: z.number().int().nonnegative(),
488
+ }).strict();
489
+
490
+ const workspaceCapabilityProfileSchema = z.object({
491
+ boundedReads: z.boolean(),
492
+ structuralTools: z.boolean(),
493
+ precisionTools: z.boolean(),
494
+ stateBookmarks: z.boolean(),
495
+ runtimeLogs: z.literal("session_local_only"),
496
+ runCapture: z.boolean(),
497
+ }).strict();
498
+
499
+ const workspaceStatusSchema = z.object({
500
+ sessionMode: z.enum(["repo_local", "daemon"]),
501
+ bindState: z.enum(["bound", "unbound"]),
502
+ repoId: z.string().nullable(),
503
+ worktreeId: z.string().nullable(),
504
+ worktreeRoot: z.string().nullable(),
505
+ gitCommonDir: z.string().nullable(),
506
+ graftDir: z.string().nullable(),
507
+ capabilityProfile: workspaceCapabilityProfileSchema.nullable(),
508
+ }).strict();
509
+
510
+ const workspaceActionSchema = workspaceStatusSchema.extend({
511
+ ok: z.boolean(),
512
+ action: z.enum(["bind", "rebind"]),
513
+ freshSessionSlice: z.boolean(),
514
+ errorCode: z.string().optional(),
515
+ error: z.string().optional(),
516
+ }).strict();
517
+
518
+ const activeCausalWorkspaceSchema = z.object({
519
+ causalContext: runtimeCausalContextSchema,
520
+ attribution: attributionSummarySchema,
521
+ latestReadEvent: readEventSchema.nullable(),
522
+ latestStageEvent: stageEventSchema.nullable(),
523
+ latestTransitionEvent: transitionEventSchema.nullable(),
524
+ repoConcurrency: repoConcurrencySummarySchema,
525
+ checkoutEpoch: z.number().int().nonnegative(),
526
+ lastTransition: repoTransitionSchema.nullable(),
527
+ semanticTransition: repoSemanticTransitionSchema.nullable(),
528
+ workspaceOverlayId: z.string().nullable(),
529
+ workspaceOverlay: workspaceOverlaySummarySchema.nullable(),
530
+ workspaceOverlayFooting: workspaceOverlayFootingSchema,
531
+ stagedTarget: runtimeStagedTargetSchema,
532
+ }).strict();
533
+
534
+ const causalStatusSchema = workspaceStatusSchema.extend({
535
+ activeCausalWorkspace: activeCausalWorkspaceSchema.nullable(),
536
+ persistedLocalHistory: persistedLocalHistorySummarySchema,
537
+ nextAction: causalSurfaceNextActionSchema,
538
+ }).strict();
539
+
540
+ const causalAttachSchema = workspaceStatusSchema.extend({
541
+ ok: z.boolean(),
542
+ action: z.literal("attach"),
543
+ activeCausalWorkspace: activeCausalWorkspaceSchema.nullable(),
544
+ persistedLocalHistory: persistedLocalHistorySummarySchema,
545
+ nextAction: causalSurfaceNextActionSchema,
546
+ errorCode: z.string().optional(),
547
+ error: z.string().optional(),
548
+ }).strict();
549
+
550
+ const activityViewSchema = workspaceStatusSchema.extend({
551
+ truthClass: z.literal("artifact_history"),
552
+ anchor: activityViewAnchorSchema,
553
+ summary: activityViewSummarySchema,
554
+ activeCausalWorkspace: z.object({
555
+ causalContext: runtimeCausalContextSchema,
556
+ attribution: attributionSummarySchema,
557
+ repoConcurrency: repoConcurrencySummarySchema.nullable(),
558
+ checkoutEpoch: z.number().int().nonnegative(),
559
+ lastTransition: repoTransitionSchema.nullable(),
560
+ semanticTransition: repoSemanticTransitionSchema.nullable(),
561
+ workspaceOverlayId: z.string().nullable(),
562
+ workspaceOverlay: workspaceOverlaySummarySchema.nullable(),
563
+ workspaceOverlayFooting: workspaceOverlayFootingSchema.nullable(),
564
+ stagedTarget: runtimeStagedTargetSchema,
565
+ }).nullable(),
566
+ activityWindow: z.object({
567
+ historyPath: z.string().nullable(),
568
+ limit: z.number().int().positive(),
569
+ returned: z.number().int().nonnegative(),
570
+ totalMatchingItems: z.number().int().nonnegative(),
571
+ truncated: z.boolean(),
572
+ missingSignalKinds: z.array(z.string()),
573
+ groups: z.array(activityViewGroupSchema),
574
+ }).strict(),
575
+ degradedReasons: z.array(z.string()),
576
+ nextAction: z.union([
577
+ causalSurfaceNextActionSchema,
578
+ z.literal("bind_workspace_to_begin_local_history"),
579
+ ]),
580
+ }).strict();
581
+
582
+ const authorizedWorkspaceSchema = z.object({
583
+ repoId: z.string(),
584
+ worktreeId: z.string(),
585
+ worktreeRoot: z.string(),
586
+ gitCommonDir: z.string(),
587
+ capabilityProfile: workspaceCapabilityProfileSchema,
588
+ authorizedAt: z.string(),
589
+ lastBoundAt: z.string().nullable(),
590
+ activeSessions: z.number().int().nonnegative(),
591
+ }).strict();
592
+
593
+ const workspaceAuthorizeSchema = z.object({
594
+ ok: z.boolean(),
595
+ changed: z.boolean(),
596
+ authorization: authorizedWorkspaceSchema.optional(),
597
+ errorCode: z.string().optional(),
598
+ error: z.string().optional(),
599
+ }).strict();
600
+
601
+ const workspaceRevokeSchema = z.object({
602
+ ok: z.boolean(),
603
+ revoked: z.boolean(),
604
+ repoId: z.string().nullable().optional(),
605
+ worktreeId: z.string().nullable().optional(),
606
+ worktreeRoot: z.string().nullable().optional(),
607
+ activeSessions: z.number().int().nonnegative().optional(),
608
+ errorCode: z.string().optional(),
609
+ error: z.string().optional(),
610
+ }).strict();
611
+
612
+ const daemonSessionSchema = z.object({
613
+ sessionId: z.string(),
614
+ sessionMode: z.literal("daemon"),
615
+ bindState: z.enum(["bound", "unbound"]),
616
+ repoId: z.string().nullable(),
617
+ worktreeId: z.string().nullable(),
618
+ worktreeRoot: z.string().nullable(),
619
+ causalSessionId: z.string().nullable(),
620
+ checkoutEpochId: z.string().nullable(),
621
+ capabilityProfile: workspaceCapabilityProfileSchema.nullable(),
622
+ startedAt: z.string(),
623
+ lastActivityAt: z.string(),
624
+ }).strict();
625
+
626
+ const daemonRepoWorktreeSchema = z.object({
627
+ worktreeId: z.string(),
628
+ worktreeRoot: z.string(),
629
+ activeSessions: z.number().int().nonnegative(),
630
+ lastBoundAt: z.string().nullable(),
631
+ }).strict();
632
+
633
+ const daemonRepoMonitorSchema = z.object({
634
+ workerKind: z.literal("git_poll_indexer"),
635
+ lifecycleState: z.enum(["running", "paused", "stopped"]),
636
+ health: z.enum(["ok", "lagging", "error", "unauthorized", "paused", "stopped"]),
637
+ lastTickAt: z.string().nullable(),
638
+ lastSuccessAt: z.string().nullable(),
639
+ lastError: z.string().nullable(),
640
+ }).strict();
641
+
642
+ const daemonRepoSchema = z.object({
643
+ repoId: z.string(),
644
+ gitCommonDir: z.string(),
645
+ authorizedWorkspaces: z.number().int().nonnegative(),
646
+ boundSessions: z.number().int().nonnegative(),
647
+ activeWorktrees: z.number().int().nonnegative(),
648
+ backlogCommits: z.number().int().nonnegative(),
649
+ lastBoundAt: z.string().nullable(),
650
+ lastActivityAt: z.string().nullable(),
651
+ monitor: daemonRepoMonitorSchema.nullable(),
652
+ worktrees: z.array(daemonRepoWorktreeSchema),
653
+ }).strict();
654
+
655
+ const monitorStatusSchema = z.object({
656
+ repoId: z.string(),
657
+ gitCommonDir: z.string(),
658
+ anchorWorktreeRoot: z.string(),
659
+ authorizedWorkspaces: z.number().int().nonnegative(),
660
+ workerKind: z.literal("git_poll_indexer"),
661
+ lifecycleState: z.enum(["running", "paused", "stopped"]),
662
+ health: z.enum(["ok", "lagging", "error", "unauthorized", "paused", "stopped"]),
663
+ pollIntervalMs: z.number().int().positive(),
664
+ lastStartedAt: z.string().nullable(),
665
+ lastTickAt: z.string().nullable(),
666
+ lastSuccessAt: z.string().nullable(),
667
+ lastError: z.string().nullable(),
668
+ lastIndexedCommit: z.string().nullable(),
669
+ lastHeadCommit: z.string().nullable(),
670
+ backlogCommits: z.number().int().nonnegative(),
671
+ lastRunCommitsIndexed: z.number().int().nonnegative(),
672
+ lastRunPatchesWritten: z.number().int().nonnegative(),
673
+ }).strict();
674
+
675
+ const monitorActionSchema = z.object({
676
+ ok: z.boolean(),
677
+ action: z.enum(["start", "pause", "resume", "stop"]),
678
+ created: z.boolean(),
679
+ changed: z.boolean(),
680
+ status: monitorStatusSchema.optional(),
681
+ errorCode: z.string().optional(),
682
+ error: z.string().optional(),
683
+ }).strict();
684
+
685
+ const daemonSchedulerSchema = z.object({
686
+ maxConcurrentJobs: z.number().int().positive(),
687
+ activeJobs: z.number().int().nonnegative(),
688
+ queuedJobs: z.number().int().nonnegative(),
689
+ interactiveQueuedJobs: z.number().int().nonnegative(),
690
+ backgroundQueuedJobs: z.number().int().nonnegative(),
691
+ activeWriterLanes: z.number().int().nonnegative(),
692
+ queuedWriterLanes: z.number().int().nonnegative(),
693
+ completedJobs: z.number().int().nonnegative(),
694
+ failedJobs: z.number().int().nonnegative(),
695
+ longestQueuedWaitMs: z.number().int().nonnegative(),
696
+ }).strict();
697
+
698
+ const daemonWorkersSchema = z.object({
699
+ mode: z.enum(["inline", "child_processes"]),
700
+ totalWorkers: z.number().int().nonnegative(),
701
+ busyWorkers: z.number().int().nonnegative(),
702
+ idleWorkers: z.number().int().nonnegative(),
703
+ queuedTasks: z.number().int().nonnegative(),
704
+ completedTasks: z.number().int().nonnegative(),
705
+ failedTasks: z.number().int().nonnegative(),
706
+ }).strict();
707
+
708
+ const daemonStatusSchema = z.object({
709
+ ok: z.literal(true),
710
+ sessionMode: z.literal("daemon"),
711
+ transport: z.enum(["unix_socket", "named_pipe"]),
712
+ sameUserOnly: z.literal(true),
713
+ socketPath: z.string(),
714
+ mcpPath: z.string(),
715
+ healthPath: z.string(),
716
+ activeSessions: z.number().int().nonnegative(),
717
+ boundSessions: z.number().int().nonnegative(),
718
+ unboundSessions: z.number().int().nonnegative(),
719
+ activeWarpRepos: z.number().int().nonnegative(),
720
+ authorizedWorkspaces: z.number().int().nonnegative(),
721
+ authorizedRepos: z.number().int().nonnegative(),
722
+ workspaceBindRequiresAuthorization: z.literal(true),
723
+ defaultCapabilityProfile: workspaceCapabilityProfileSchema,
724
+ totalMonitors: z.number().int().nonnegative(),
725
+ runningMonitors: z.number().int().nonnegative(),
726
+ pausedMonitors: z.number().int().nonnegative(),
727
+ stoppedMonitors: z.number().int().nonnegative(),
728
+ failingMonitors: z.number().int().nonnegative(),
729
+ backlogMonitors: z.number().int().nonnegative(),
730
+ scheduler: daemonSchedulerSchema,
731
+ workers: daemonWorkersSchema,
732
+ startedAt: z.string(),
733
+ }).strict();
734
+
735
+ function extendWithCommonFields(
736
+ schema: z.ZodType,
737
+ common: z.ZodRawShape,
738
+ ): z.ZodType {
739
+ if (schema instanceof z.ZodObject) {
740
+ return schema.extend(common).strict();
741
+ }
742
+ if (schema instanceof z.ZodUnion) {
743
+ return z.union(schema.options.map((option) => {
744
+ if (!(option instanceof z.ZodObject)) {
745
+ throw new Error("Output schema unions must be composed of objects");
746
+ }
747
+ return option.extend(common).strict();
748
+ }) as [z.ZodObject, z.ZodObject, ...z.ZodObject[]]);
749
+ }
750
+ throw new Error("Output schemas must be objects or unions of objects");
751
+ }
752
+
753
+ function withMcpCommon(
754
+ tool: McpToolName,
755
+ schema: z.ZodType,
756
+ ): z.ZodType {
757
+ return extendWithCommonFields(schema, {
758
+ _schema: schemaMetaLiteral(mcpOutputSchemaMeta[tool]),
759
+ _receipt: receiptSchema,
760
+ tripwire: z.array(tripwireSchema).optional(),
761
+ });
762
+ }
763
+
764
+ function withCliCommon(
765
+ command: CliCommandName,
766
+ schema: z.ZodType,
767
+ ): z.ZodType {
768
+ return extendWithCommonFields(schema, {
769
+ _schema: schemaMetaLiteral(cliOutputSchemaMeta[command]),
770
+ });
771
+ }
772
+
773
+ function withCliPeerCommon(
774
+ command: CliCommandName,
775
+ schema: z.ZodType,
776
+ ): z.ZodType {
777
+ return extendWithCommonFields(schema, {
778
+ _schema: schemaMetaLiteral(cliOutputSchemaMeta[command]),
779
+ _receipt: receiptSchema,
780
+ tripwire: z.array(tripwireSchema).optional(),
781
+ });
782
+ }
783
+
784
+ const mcpOutputBodySchemas: Record<McpToolName, z.ZodType> = {
785
+ safe_read: z.object({
786
+ path: z.string(),
787
+ projection: z.enum(["content", "outline", "refused", "error", "cache_hit", "diff"]),
788
+ reason: z.string(),
789
+ actual: actualSchema.optional(),
790
+ thresholds: thresholdsSchema.optional(),
791
+ sessionDepth: sessionDepthSchema.optional(),
792
+ content: z.string().optional(),
793
+ outline: z.array(outlineEntrySchema).optional(),
794
+ jumpTable: z.array(jumpEntrySchema).optional(),
795
+ estimatedBytesAvoided: z.number().int().nonnegative().optional(),
796
+ next: z.array(z.string()).optional(),
797
+ reasonDetail: z.string().optional(),
798
+ readCount: z.number().int().nonnegative().optional(),
799
+ lastReadAt: z.string().optional(),
800
+ diff: outlineDiffSchema.optional(),
801
+ }).strict(),
802
+ file_outline: z.union([
803
+ z.object({
804
+ path: z.string(),
805
+ outline: z.array(outlineEntrySchema),
806
+ jumpTable: z.array(jumpEntrySchema),
807
+ partial: z.boolean().optional(),
808
+ reason: z.string().optional(),
809
+ error: z.string().optional(),
810
+ cacheHit: z.boolean().optional(),
811
+ }).strict(),
812
+ z.object({
813
+ path: z.string(),
814
+ projection: z.literal("refused"),
815
+ reason: z.string(),
816
+ reasonDetail: z.string().optional(),
817
+ next: z.array(z.string()).optional(),
818
+ actual: actualSchema.optional(),
819
+ }).strict(),
820
+ ]),
821
+ read_range: z.object({
822
+ path: z.string(),
823
+ content: z.string().optional(),
824
+ startLine: z.number().int().positive().optional(),
825
+ endLine: z.number().int().positive().optional(),
826
+ reason: z.string().optional(),
827
+ truncated: z.boolean().optional(),
828
+ clipped: z.boolean().optional(),
829
+ projection: z.literal("refused").optional(),
830
+ reasonDetail: z.string().optional(),
831
+ next: z.array(z.string()).optional(),
832
+ actual: actualSchema.optional(),
833
+ }).strict(),
834
+ changed_since: z.object({
835
+ status: z.enum(["file_not_found", "refused", "unsupported", "unchanged", "no_previous_observation"]).optional(),
836
+ reason: z.string().optional(),
837
+ diff: outlineDiffSchema.optional(),
838
+ consumed: z.boolean().optional(),
839
+ }).strict(),
840
+ graft_diff: z.object({
841
+ base: z.string(),
842
+ head: z.string(),
843
+ files: z.array(fileDiffSchema),
844
+ refused: z.array(structuralRefusalSchema).optional(),
845
+ layer: worldlineLayerSchema,
846
+ }).strict(),
847
+ graft_since: z.object({
848
+ base: z.string(),
849
+ head: z.string(),
850
+ files: z.array(fileDiffSchema),
851
+ refused: z.array(structuralRefusalSchema).optional(),
852
+ summary: z.string(),
853
+ layer: z.literal("ref_view"),
854
+ }).strict(),
855
+ graft_map: z.object({
856
+ directory: z.string(),
857
+ files: z.array(mapFileSchema),
858
+ refused: z.array(structuralRefusalSchema).optional(),
859
+ summary: z.string(),
860
+ }).strict(),
861
+ code_show: z.object({
862
+ symbol: z.string().optional(),
863
+ kind: z.string().optional(),
864
+ signature: z.string().optional(),
865
+ path: z.string().optional(),
866
+ exported: z.boolean().optional(),
867
+ startLine: z.number().int().positive().optional(),
868
+ endLine: z.number().int().positive().optional(),
869
+ content: z.string().optional(),
870
+ truncated: z.boolean().optional(),
871
+ clipped: z.boolean().optional(),
872
+ source: z.enum(["warp", "live"]),
873
+ layer: worldlineLayerSchema,
874
+ ambiguous: z.boolean().optional(),
875
+ matches: z.array(precisionSymbolMatchSchema).optional(),
876
+ error: z.string().optional(),
877
+ projection: z.literal("refused").optional(),
878
+ reason: z.string().optional(),
879
+ reasonDetail: z.string().optional(),
880
+ next: z.array(z.string()).optional(),
881
+ actual: actualSchema.optional(),
882
+ }).strict(),
883
+ code_find: z.object({
884
+ query: z.string(),
885
+ kind: z.string().nullable(),
886
+ matches: z.array(precisionSymbolMatchSchema).optional(),
887
+ total: z.number().int().nonnegative().optional(),
888
+ path: z.string().optional(),
889
+ projection: z.literal("refused").optional(),
890
+ reason: z.string().optional(),
891
+ reasonDetail: z.string().optional(),
892
+ next: z.array(z.string()).optional(),
893
+ actual: actualSchema.optional(),
894
+ source: z.enum(["warp", "live"]),
895
+ layer: worldlineLayerSchema,
896
+ }).strict(),
897
+ code_refs: z.object({
898
+ query: z.string(),
899
+ mode: z.enum(["text", "import", "call", "property"]),
900
+ scope: z.string(),
901
+ matches: z.array(codeRefsMatchSchema).optional(),
902
+ total: z.number().int().nonnegative().optional(),
903
+ path: z.string().optional(),
904
+ projection: z.literal("refused").optional(),
905
+ reason: z.string().optional(),
906
+ reasonDetail: z.string().optional(),
907
+ next: z.array(z.string()).optional(),
908
+ actual: actualSchema.optional(),
909
+ source: z.literal("text_fallback"),
910
+ provenance: codeRefsProvenanceSchema,
911
+ layer: worldlineLayerSchema,
912
+ }).strict(),
913
+ daemon_repos: z.object({
914
+ repos: z.array(daemonRepoSchema),
915
+ filter: z.object({
916
+ repoId: z.string().optional(),
917
+ cwd: z.string().optional(),
918
+ }).strict().optional(),
919
+ }).strict(),
920
+ daemon_status: daemonStatusSchema,
921
+ daemon_sessions: z.object({
922
+ sessions: z.array(daemonSessionSchema),
923
+ }).strict(),
924
+ daemon_monitors: z.object({
925
+ monitors: z.array(monitorStatusSchema),
926
+ }).strict(),
927
+ monitor_start: monitorActionSchema,
928
+ monitor_pause: monitorActionSchema,
929
+ monitor_resume: monitorActionSchema,
930
+ monitor_stop: monitorActionSchema,
931
+ workspace_authorize: workspaceAuthorizeSchema,
932
+ workspace_authorizations: z.object({
933
+ workspaces: z.array(authorizedWorkspaceSchema),
934
+ }).strict(),
935
+ workspace_revoke: workspaceRevokeSchema,
936
+ workspace_bind: workspaceActionSchema.extend({
937
+ action: z.literal("bind"),
938
+ }).strict(),
939
+ workspace_status: workspaceStatusSchema,
940
+ activity_view: activityViewSchema,
941
+ causal_status: causalStatusSchema,
942
+ causal_attach: causalAttachSchema,
943
+ workspace_rebind: workspaceActionSchema.extend({
944
+ action: z.literal("rebind"),
945
+ }).strict(),
946
+ run_capture: z.object({
947
+ output: z.string(),
948
+ totalLines: z.number().int().nonnegative(),
949
+ tailedLines: z.number().int().nonnegative(),
950
+ logPath: z.string().nullable().optional(),
951
+ logRedactions: z.number().int().nonnegative().optional(),
952
+ logPersistenceEnabled: z.boolean().optional(),
953
+ truncated: z.boolean(),
954
+ disabled: z.boolean().optional(),
955
+ error: z.string().optional(),
956
+ stderr: z.string().optional(),
957
+ policyBoundary: policyBoundarySchema,
958
+ }).strict(),
959
+ state_save: z.object({
960
+ ok: z.boolean(),
961
+ reason: z.string().optional(),
962
+ }).strict(),
963
+ state_load: z.object({
964
+ content: z.string().nullable(),
965
+ }).strict(),
966
+ set_budget: z.object({
967
+ budget: budgetSchema.nullable(),
968
+ }).strict(),
969
+ explain: z.object({
970
+ code: z.string(),
971
+ meaning: z.string().optional(),
972
+ action: z.string().optional(),
973
+ error: z.string().optional(),
974
+ knownCodes: z.string().optional(),
975
+ }).strict(),
976
+ doctor: z.object({
977
+ projectRoot: z.string(),
978
+ parserHealthy: z.boolean(),
979
+ thresholds: thresholdsSchema,
980
+ sessionDepth: z.enum(["early", "mid", "late"]),
981
+ totalMessages: z.number().int().nonnegative(),
982
+ burdenSummary: burdenSummarySchema,
983
+ runtimeObservability: runtimeObservabilitySchema,
984
+ causalContext: runtimeCausalContextSchema,
985
+ latestReadEvent: readEventSchema.nullable(),
986
+ latestStageEvent: stageEventSchema.nullable(),
987
+ latestTransitionEvent: transitionEventSchema.nullable(),
988
+ repoConcurrency: repoConcurrencySummarySchema.nullable(),
989
+ checkoutEpoch: z.number().int().nonnegative(),
990
+ lastTransition: repoTransitionSchema.nullable(),
991
+ semanticTransition: repoSemanticTransitionSchema.nullable(),
992
+ workspaceOverlayId: z.string().nullable(),
993
+ workspaceOverlay: workspaceOverlaySummarySchema.nullable(),
994
+ workspaceOverlayFooting: workspaceOverlayFootingSchema,
995
+ stagedTarget: runtimeStagedTargetSchema,
996
+ attribution: attributionSummarySchema,
997
+ persistedLocalHistory: persistedLocalHistorySummarySchema,
998
+ recommendedNextAction: causalSurfaceNextActionSchema,
999
+ }).strict(),
1000
+ stats: z.object({
1001
+ totalReads: z.number().int().nonnegative(),
1002
+ totalOutlines: z.number().int().nonnegative(),
1003
+ totalRefusals: z.number().int().nonnegative(),
1004
+ totalCacheHits: z.number().int().nonnegative(),
1005
+ totalBytesReturned: z.number().int().nonnegative(),
1006
+ totalBytesAvoidedByCache: z.number().int().nonnegative(),
1007
+ totalNonReadBytesReturned: z.number().int().nonnegative(),
1008
+ burdenByKind: burdenByKindSchema,
1009
+ }).strict(),
1010
+ };
1011
+
1012
+ export const MCP_OUTPUT_SCHEMAS: Record<McpToolName, z.ZodType> = {
1013
+ safe_read: withMcpCommon("safe_read", mcpOutputBodySchemas.safe_read),
1014
+ file_outline: withMcpCommon("file_outline", mcpOutputBodySchemas.file_outline),
1015
+ read_range: withMcpCommon("read_range", mcpOutputBodySchemas.read_range),
1016
+ changed_since: withMcpCommon("changed_since", mcpOutputBodySchemas.changed_since),
1017
+ graft_diff: withMcpCommon("graft_diff", mcpOutputBodySchemas.graft_diff),
1018
+ graft_since: withMcpCommon("graft_since", mcpOutputBodySchemas.graft_since),
1019
+ graft_map: withMcpCommon("graft_map", mcpOutputBodySchemas.graft_map),
1020
+ code_show: withMcpCommon("code_show", mcpOutputBodySchemas.code_show),
1021
+ code_find: withMcpCommon("code_find", mcpOutputBodySchemas.code_find),
1022
+ code_refs: withMcpCommon("code_refs", mcpOutputBodySchemas.code_refs),
1023
+ daemon_repos: withMcpCommon("daemon_repos", mcpOutputBodySchemas.daemon_repos),
1024
+ daemon_status: withMcpCommon("daemon_status", mcpOutputBodySchemas.daemon_status),
1025
+ daemon_sessions: withMcpCommon("daemon_sessions", mcpOutputBodySchemas.daemon_sessions),
1026
+ daemon_monitors: withMcpCommon("daemon_monitors", mcpOutputBodySchemas.daemon_monitors),
1027
+ monitor_start: withMcpCommon("monitor_start", mcpOutputBodySchemas.monitor_start),
1028
+ monitor_pause: withMcpCommon("monitor_pause", mcpOutputBodySchemas.monitor_pause),
1029
+ monitor_resume: withMcpCommon("monitor_resume", mcpOutputBodySchemas.monitor_resume),
1030
+ monitor_stop: withMcpCommon("monitor_stop", mcpOutputBodySchemas.monitor_stop),
1031
+ workspace_authorize: withMcpCommon("workspace_authorize", mcpOutputBodySchemas.workspace_authorize),
1032
+ workspace_authorizations: withMcpCommon(
1033
+ "workspace_authorizations",
1034
+ mcpOutputBodySchemas.workspace_authorizations,
1035
+ ),
1036
+ workspace_revoke: withMcpCommon("workspace_revoke", mcpOutputBodySchemas.workspace_revoke),
1037
+ workspace_bind: withMcpCommon("workspace_bind", mcpOutputBodySchemas.workspace_bind),
1038
+ workspace_status: withMcpCommon("workspace_status", mcpOutputBodySchemas.workspace_status),
1039
+ activity_view: withMcpCommon("activity_view", mcpOutputBodySchemas.activity_view),
1040
+ causal_status: withMcpCommon("causal_status", mcpOutputBodySchemas.causal_status),
1041
+ causal_attach: withMcpCommon("causal_attach", mcpOutputBodySchemas.causal_attach),
1042
+ workspace_rebind: withMcpCommon("workspace_rebind", mcpOutputBodySchemas.workspace_rebind),
1043
+ run_capture: withMcpCommon("run_capture", mcpOutputBodySchemas.run_capture),
1044
+ state_save: withMcpCommon("state_save", mcpOutputBodySchemas.state_save),
1045
+ state_load: withMcpCommon("state_load", mcpOutputBodySchemas.state_load),
1046
+ set_budget: withMcpCommon("set_budget", mcpOutputBodySchemas.set_budget),
1047
+ explain: withMcpCommon("explain", mcpOutputBodySchemas.explain),
1048
+ doctor: withMcpCommon("doctor", mcpOutputBodySchemas.doctor),
1049
+ stats: withMcpCommon("stats", mcpOutputBodySchemas.stats),
1050
+ };
1051
+
1052
+ const initActionSchema = z.object({
1053
+ action: z.enum(["exists", "create", "append"]),
1054
+ label: z.string(),
1055
+ detail: z.string().optional(),
1056
+ }).strict();
1057
+
1058
+ const hooksConfigSchema = z.object({
1059
+ hooks: z.object({
1060
+ PreToolUse: z.array(z.object({
1061
+ matcher: z.literal("Read"),
1062
+ hooks: z.array(z.object({
1063
+ type: z.literal("command"),
1064
+ command: z.string(),
1065
+ }).strict()),
1066
+ }).strict()),
1067
+ PostToolUse: z.array(z.object({
1068
+ matcher: z.literal("Read"),
1069
+ hooks: z.array(z.object({
1070
+ type: z.literal("command"),
1071
+ command: z.string(),
1072
+ }).strict()),
1073
+ }).strict()),
1074
+ }).strict(),
1075
+ }).strict();
1076
+
1077
+ const suggestedMcpServerSchema = z.object({
1078
+ mcpServers: z.object({
1079
+ graft: z.object({
1080
+ command: z.literal("npx"),
1081
+ args: z.tuple([z.literal("-y"), z.literal("@flyingrobots/graft"), z.literal("serve")]),
1082
+ }).strict(),
1083
+ }).strict(),
1084
+ }).strict();
1085
+
1086
+ export const CLI_OUTPUT_SCHEMAS: Record<CliCommandName, z.ZodType> = {
1087
+ init: withCliCommon("init", z.object({
1088
+ ok: z.boolean(),
1089
+ cwd: z.string(),
1090
+ actions: z.array(initActionSchema).optional(),
1091
+ hooksConfig: hooksConfigSchema.optional(),
1092
+ suggestedMcpServer: suggestedMcpServerSchema.optional(),
1093
+ error: z.string().optional(),
1094
+ }).strict()),
1095
+ index: withCliCommon("index", z.object({
1096
+ ok: z.boolean(),
1097
+ cwd: z.string(),
1098
+ from: z.string().nullable(),
1099
+ commitsIndexed: z.number().int().nonnegative().optional(),
1100
+ patchesWritten: z.number().int().nonnegative().optional(),
1101
+ error: z.string().optional(),
1102
+ }).strict()),
1103
+ read_safe: withCliPeerCommon("read_safe", mcpOutputBodySchemas.safe_read),
1104
+ read_outline: withCliPeerCommon("read_outline", mcpOutputBodySchemas.file_outline),
1105
+ read_range: withCliPeerCommon("read_range", mcpOutputBodySchemas.read_range),
1106
+ read_changed: withCliPeerCommon("read_changed", mcpOutputBodySchemas.changed_since),
1107
+ struct_diff: withCliPeerCommon("struct_diff", mcpOutputBodySchemas.graft_diff),
1108
+ struct_since: withCliPeerCommon("struct_since", mcpOutputBodySchemas.graft_since),
1109
+ struct_map: withCliPeerCommon("struct_map", mcpOutputBodySchemas.graft_map),
1110
+ symbol_show: withCliPeerCommon("symbol_show", mcpOutputBodySchemas.code_show),
1111
+ symbol_find: withCliPeerCommon("symbol_find", mcpOutputBodySchemas.code_find),
1112
+ diag_doctor: withCliPeerCommon("diag_doctor", mcpOutputBodySchemas.doctor),
1113
+ diag_activity: withCliPeerCommon("diag_activity", mcpOutputBodySchemas.activity_view),
1114
+ diag_explain: withCliPeerCommon("diag_explain", mcpOutputBodySchemas.explain),
1115
+ diag_stats: withCliPeerCommon("diag_stats", mcpOutputBodySchemas.stats),
1116
+ diag_capture: withCliPeerCommon("diag_capture", mcpOutputBodySchemas.run_capture),
1117
+ };
1118
+
1119
+ export function getMcpOutputSchemaMeta(tool: McpToolName): OutputSchemaMeta {
1120
+ return mcpOutputSchemaMeta[tool];
1121
+ }
1122
+
1123
+ export function getCliOutputSchemaMeta(command: CliCommandName): OutputSchemaMeta {
1124
+ return cliOutputSchemaMeta[command];
1125
+ }
1126
+
1127
+ export function getMcpOutputSchema(tool: McpToolName): z.ZodType {
1128
+ return MCP_OUTPUT_SCHEMAS[tool];
1129
+ }
1130
+
1131
+ export function getCliOutputSchema(command: CliCommandName): z.ZodType {
1132
+ return CLI_OUTPUT_SCHEMAS[command];
1133
+ }
1134
+
1135
+ export function attachMcpSchemaMeta<T extends object>(
1136
+ tool: McpToolName,
1137
+ data: T,
1138
+ ): T & { _schema: OutputSchemaMeta } {
1139
+ return { ...data, _schema: getMcpOutputSchemaMeta(tool) };
1140
+ }
1141
+
1142
+ export function attachCliSchemaMeta<T extends object>(
1143
+ command: CliCommandName,
1144
+ data: T,
1145
+ ): T & { _schema: OutputSchemaMeta } {
1146
+ return { ...data, _schema: getCliOutputSchemaMeta(command) };
1147
+ }
1148
+
1149
+ export function validateCliOutput(
1150
+ command: CliCommandName,
1151
+ data: unknown,
1152
+ ): Record<string, unknown> {
1153
+ return CLI_OUTPUT_SCHEMAS[command].parse(data) as Record<string, unknown>;
1154
+ }
1155
+
1156
+ export function cliCommandMcpTool(command: CliCommandName): McpToolName | null {
1157
+ return CLI_COMMAND_TO_MCP_TOOL[command] ?? null;
1158
+ }
1159
+
1160
+ export function getMcpOutputJsonSchema(tool: McpToolName): unknown {
1161
+ return z.toJSONSchema(MCP_OUTPUT_SCHEMAS[tool]);
1162
+ }
1163
+
1164
+ export function getCliOutputJsonSchema(command: CliCommandName): unknown {
1165
+ return z.toJSONSchema(CLI_OUTPUT_SCHEMAS[command]);
1166
+ }
1167
+
1168
+ export const RECEIPT_SCHEMA = receiptSchema;
1169
+ export const RECEIPT_JSON_SCHEMA = z.toJSONSchema(receiptSchema);