@bendyline/gezel 1.0.2 → 1.0.4

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.
@@ -494,13 +494,14 @@ var GateCheckSchema = z3.discriminatedUnion("kind", [
494
494
  label: z3.string().min(1).optional(),
495
495
  artifact: z3.boolean().optional()
496
496
  }),
497
- /** `file` must NOT match `pattern` (regex). E.g. exclude internal-only release noise. */
497
+ /** `file` must NOT match `pattern` (regex), in the workspace or artifacts drawer. */
498
498
  z3.object({
499
499
  kind: z3.literal("notContains"),
500
500
  file: z3.string().min(1),
501
501
  pattern: z3.string().min(1),
502
502
  flags: z3.string().optional(),
503
- label: z3.string().min(1).optional()
503
+ label: z3.string().min(1).optional(),
504
+ artifact: z3.boolean().optional()
504
505
  }),
505
506
  /**
506
507
  * `file` may use high-risk claim wording only when the exact matched
@@ -643,6 +644,20 @@ var GateCheckSchema = z3.discriminatedUnion("kind", [
643
644
  tools: z3.array(z3.string().min(1)).min(1),
644
645
  minSuccessful: z3.number().int().positive().optional()
645
646
  }),
647
+ /**
648
+ * A PR-review coverage ledger must name every changed path materialized in
649
+ * a connector corpus. The ledger is workspace JSON (`reviewedFiles` by
650
+ * default); the source records live in the read-only artifacts drawer and
651
+ * carry their authoritative path in frontmatter. This prevents a polished
652
+ * report from passing after only the first context-sized prefix was read.
653
+ */
654
+ z3.object({
655
+ kind: z3.literal("corpusCoverage"),
656
+ file: z3.string().min(1),
657
+ corpusDir: z3.string().min(1),
658
+ reviewedField: z3.string().min(1).optional(),
659
+ recordField: z3.string().min(1).optional()
660
+ }),
646
661
  /**
647
662
  * The H1 slide titles in `file` must match the numbered slide headings in
648
663
  * `outlineFile`, one-for-one and in order. This makes a locked Markdown
@@ -652,7 +667,9 @@ var GateCheckSchema = z3.discriminatedUnion("kind", [
652
667
  z3.object({
653
668
  kind: z3.literal("markdownHeadingsMatch"),
654
669
  file: z3.string().min(1),
655
- outlineFile: z3.string().min(1)
670
+ outlineFile: z3.string().min(1),
671
+ /** Read outlineFile from artifacts while file uses the check's primary surface. */
672
+ outlineArtifact: z3.boolean().optional()
656
673
  }),
657
674
  /**
658
675
  * Named facts in `file` must come from authorized sources: each fact
@@ -915,6 +932,12 @@ var AdvanceWhenSchema = z5.object({
915
932
  /** Step to activate on the signal. Defaults to `next`; must resolve like `next`. */
916
933
  goto: z5.string().optional()
917
934
  });
935
+ var CraftbookStepInputSchema = z5.object({
936
+ /** Path relative to the selected drawer's root. */
937
+ file: z5.string().min(1).describe("Path relative to the selected drawer root."),
938
+ /** Read from the artifacts drawer instead of the project workspace. */
939
+ artifact: z5.boolean().optional().describe("True for the artifacts drawer; false/omitted for the project workspace.")
940
+ });
918
941
  var ModelTierSchema = z5.enum(MODEL_TIER_ORDER);
919
942
  var CraftbookStepSchema = z5.object({
920
943
  id: z5.string().min(1),
@@ -955,6 +978,10 @@ var CraftbookStepSchema = z5.object({
955
978
  * read the LAST ref's output (legacy routing; prefer gate routing).
956
979
  */
957
980
  onExit: ScriptRefListSchema.optional(),
981
+ /** Required file inputs for this step, in the order they should be opened. */
982
+ consumes: z5.array(CraftbookStepInputSchema).min(1).optional().describe(
983
+ "Files this step must open before working. Artifact inputs also require an explicit `read_artifact` call in the step prompt."
984
+ ),
958
985
  /** See {@link AdvanceWhenSchema}. */
959
986
  advanceWhen: AdvanceWhenSchema.optional(),
960
987
  /** The end-of-step decision. See {@link StepGateSchema} (current) / {@link GateSpecSchema} (legacy). */
@@ -966,14 +993,16 @@ var CraftbookStepSchema = z5.object({
966
993
  * Marks the parent step that triggers a declarative per-item fanout
967
994
  * (see {@link CraftbookSpawnSchema}). When this step activates on a
968
995
  * spawn-host task, the runtime reads the craftbook's `spawn.overFile`
969
- * workspace JSON array and spawns one child task per item — no model
996
+ * JSON array on its declared surface and spawns one child task per item — no model
970
997
  * tool call. Inert unless the craftbook also declares `spawn`.
971
998
  */
972
999
  spawnFanout: z5.boolean().optional()
973
1000
  });
974
1001
  var CraftbookSpawnSchema = z5.object({
975
- /** Workspace-relative JSON file the parent produces; its array drives the fanout. */
1002
+ /** Surface-relative JSON file the parent produces; its array drives the fanout. */
976
1003
  overFile: z5.string().min(1),
1004
+ /** Read overFile from the artifacts drawer instead of the project workspace. */
1005
+ overArtifact: z5.boolean().optional(),
977
1006
  /** Dotted path to the array inside `overFile`. Absent → the file itself is the array. */
978
1007
  itemsPath: z5.string().optional(),
979
1008
  /** Entry step id of the child template. Defaults to the first `steps` entry. */
@@ -1009,6 +1038,14 @@ function validateCraftbookGraph(cb) {
1009
1038
  problems.push(`step "${s.id}" advanceWhen.goto "${s.advanceWhen.goto}" missing from steps`);
1010
1039
  }
1011
1040
  }
1041
+ for (const input of s.consumes ?? []) {
1042
+ if (!input.artifact) continue;
1043
+ if (!/`read_artifact(?:`|\()/.test(s.prompt ?? "")) {
1044
+ problems.push(
1045
+ `step "${s.id}" consumes artifact "${input.file}" but its prompt does not explicitly call \`read_artifact\``
1046
+ );
1047
+ }
1048
+ }
1012
1049
  if (s.gate) {
1013
1050
  const gate = normalizeStepGate(s.gate);
1014
1051
  if (s.terminal && gate.at === "activation") {
@@ -1301,6 +1338,9 @@ var NewCraftbookStepSchema = z5.object({
1301
1338
  assignee: TaskAssigneeSchema.optional(),
1302
1339
  onEnter: ScriptRefListSchema.optional(),
1303
1340
  onExit: ScriptRefListSchema.optional(),
1341
+ consumes: z5.array(CraftbookStepInputSchema).min(1).optional().describe(
1342
+ "Files this step must open before working. Artifact inputs also require an explicit `read_artifact` call in the step prompt."
1343
+ ),
1304
1344
  advanceWhen: AdvanceWhenSchema.optional(),
1305
1345
  gate: StepGateUnionSchema.optional(),
1306
1346
  /** See {@link StepDeliverableSchema} — one field attaches the enforced gate. */
@@ -1413,6 +1453,7 @@ function resolveSteps(blueprints) {
1413
1453
  ...s.assignee ? { assignee: s.assignee } : {},
1414
1454
  ...s.onEnter ? { onEnter: s.onEnter } : {},
1415
1455
  ...s.onExit ? { onExit: s.onExit } : {},
1456
+ ...s.consumes && s.consumes.length > 0 ? { consumes: s.consumes } : {},
1416
1457
  ...s.advanceWhen ? { advanceWhen: s.advanceWhen } : {},
1417
1458
  ...s.gate ? { gate: s.gate } : {},
1418
1459
  ...s.next ? { next: s.next } : {},
@@ -1535,6 +1576,10 @@ function applyStepPatch(step, patch) {
1535
1576
  delete updated.onExit;
1536
1577
  } else updated.onExit = patch.onExit;
1537
1578
  }
1579
+ if (patch.consumes !== void 0) {
1580
+ if (patch.consumes === null || patch.consumes.length === 0) delete updated.consumes;
1581
+ else updated.consumes = patch.consumes;
1582
+ }
1538
1583
  if (patch.advanceWhen !== void 0) {
1539
1584
  if (patch.advanceWhen === null) delete updated.advanceWhen;
1540
1585
  else updated.advanceWhen = patch.advanceWhen;
@@ -1713,7 +1758,7 @@ function craftbookDocFormatFromEnv(value) {
1713
1758
  }
1714
1759
 
1715
1760
  // src/schemas/craftbook-test.ts
1716
- import { z as z19 } from "zod";
1761
+ import { z as z20 } from "zod";
1717
1762
 
1718
1763
  // src/schemas/history.ts
1719
1764
  import { z as z18 } from "zod";
@@ -3534,7 +3579,15 @@ var ChatEventSchema = z17.discriminatedUnion("type", [
3534
3579
  kind: z17.string(),
3535
3580
  summary: z17.string(),
3536
3581
  at: z17.string(),
3537
- taskRef: z17.string().optional()
3582
+ taskRef: z17.string().optional(),
3583
+ /**
3584
+ * Gezel responsible for the event, when History recorded one. Kept
3585
+ * separate from the human-readable summary so fixed-presentation
3586
+ * clients (notably the role-name-only CLI) can render the actor using
3587
+ * their own naming mode instead of leaking the friendly name embedded
3588
+ * in the audit prose.
3589
+ */
3590
+ gezelId: z17.string().optional()
3538
3591
  }),
3539
3592
  /**
3540
3593
  * Emitted when a gezel crosses a growth level threshold and a pending
@@ -4075,444 +4128,9 @@ var ListHistoryResponseSchema = z18.object({
4075
4128
  entries: z18.array(HistoryEntrySchema)
4076
4129
  });
4077
4130
 
4078
- // src/schemas/craftbook-test.ts
4079
- var CRAFTBOOK_TEST_SCHEMA_VERSION = 1;
4080
- var CRAFTBOOK_TEST_FILENAME = "test.json";
4081
- var PrometheusAlertsCheckSchema = z19.object({
4082
- kind: z19.literal("prometheusAlerts"),
4083
- file: z19.string().min(1),
4084
- minRules: z19.number().int().positive().optional(),
4085
- maxPageAlerts: z19.number().int().nonnegative().optional(),
4086
- allowedSeverities: z19.array(z19.string().min(1)).optional(),
4087
- requiredServices: z19.array(z19.string().min(1)).optional(),
4088
- requiredRunbookUrls: z19.array(z19.string().min(1)).optional()
4089
- }).strict();
4090
- var NodeScriptPassesCheckSchema = z19.object({
4091
- kind: z19.literal("nodeScriptPasses"),
4092
- script: z19.string().min(1),
4093
- timeoutMs: z19.number().int().positive().optional(),
4094
- requiredOutput: z19.array(
4095
- z19.object({
4096
- pattern: z19.string().min(1),
4097
- flags: z19.string().optional(),
4098
- label: z19.string().optional()
4099
- }).strict()
4100
- ).optional()
4101
- }).strict();
4102
- var BinaryDocumentCheckSchema = z19.object({
4103
- kind: z19.literal("binaryDocument"),
4104
- file: z19.string().min(1),
4105
- /** Look in the artifacts drawer instead of the workspace. */
4106
- artifact: z19.boolean().optional(),
4107
- /** Floor on the container's byte length; defaults to 1000. */
4108
- minBytes: z19.number().int().positive().optional()
4109
- }).strict();
4110
- var CraftbookTestCheckSchema = z19.union([
4111
- GateCheckSchema,
4112
- PrometheusAlertsCheckSchema,
4113
- NodeScriptPassesCheckSchema,
4114
- BinaryDocumentCheckSchema
4115
- ]);
4116
- var MockServiceIdSchema = z19.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "mock service ids are lowercase kebab-case");
4117
- var MockToolsetIdSchema = z19.string().min(1).regex(
4118
- /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,
4119
- "mock toolset ids are lowercase catalog ids or scoped npm-style ids"
4120
- );
4121
- var MockHttpRouteSchema = z19.object({
4122
- method: z19.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
4123
- /** Exact path, `:param` segments, or a trailing `*` wildcard. */
4124
- path: z19.string().min(1),
4125
- status: z19.number().int().min(100).max(599).default(200),
4126
- headers: z19.record(z19.string(), z19.string()).optional(),
4127
- /** String bodies are served verbatim; anything else is JSON-encoded. */
4128
- body: z19.unknown(),
4129
- latencyMs: z19.number().int().nonnegative().optional()
4130
- }).strict();
4131
- var MockServiceSchema = z19.discriminatedUnion("kind", [
4132
- z19.object({
4133
- kind: z19.literal("http"),
4134
- id: MockServiceIdSchema,
4135
- description: z19.string().min(1),
4136
- /**
4137
- * v1 mock HTTP is reachable ONLY through the `http.authed`
4138
- * credential rail (anonymous script/browser HTTP hard-rejects
4139
- * loopback), so a credential is required. The harness seeds it and
4140
- * grants the mock's exact origin on the trial project.
4141
- */
4142
- credential: z19.object({
4143
- name: z19.string().regex(/^mock\.[a-z0-9][a-z0-9.-]*$/, "mock credentials are named mock.<service-id>"),
4144
- authScheme: z19.enum(["bearer", "basic"]).optional()
4145
- }).strict(),
4146
- routes: z19.array(MockHttpRouteSchema).min(1)
4147
- }).strict(),
4148
- z19.object({
4149
- kind: z19.literal("webhook"),
4150
- id: MockServiceIdSchema,
4151
- description: z19.string().min(1),
4152
- /** Receiver path; defaults to `/webhook` when omitted. */
4153
- path: z19.string().min(1).optional()
4154
- }).strict(),
4155
- z19.object({
4156
- kind: z19.literal("cli"),
4157
- id: MockServiceIdSchema,
4158
- description: z19.string().min(1),
4159
- /** Fake-CLI shim seeded as a workspace file (today's dry-run pattern). */
4160
- shim: z19.object({ path: z19.string().min(1), content: z19.string() }).strict()
4161
- }).strict(),
4162
- z19.object({
4163
- kind: z19.literal("mcp"),
4164
- id: MockServiceIdSchema,
4165
- description: z19.string().min(1),
4166
- /** Override the local-catalog id when the mock replaces a real dependency. */
4167
- toolsetId: MockToolsetIdSchema.optional(),
4168
- /**
4169
- * Served live by the eval mock rail: each declared tool becomes a
4170
- * real tool on a per-trial Streamable-HTTP MCP endpoint, installed
4171
- * into the trial via a local-catalog `mock-mcp-<id>` toolset.
4172
- * `resultTemplate` is JSON-encoded as the tool result text
4173
- * (default `{"ok":true}` when absent).
4174
- */
4175
- tools: z19.array(
4176
- z19.object({
4177
- name: z19.string().min(1),
4178
- description: z19.string().min(1),
4179
- resultTemplate: z19.unknown().optional(),
4180
- /** Deterministic stateful responses, consumed in call order; the last repeats. */
4181
- resultSequence: z19.array(z19.unknown()).min(1).optional(),
4182
- /**
4183
- * Deterministic eval-only file materialization after this
4184
- * tool call. The fixture must match the container the
4185
- * deliverable's extension claims — a `minimal-pptx` written
4186
- * to a `.docx` path now fails the `binaryDocument` check on
4187
- * content type rather than sliding through a byte floor.
4188
- */
4189
- writeFixture: z19.object({
4190
- surface: z19.enum(["workspace", "artifact"]),
4191
- pathArgument: z19.string().min(1),
4192
- fixture: z19.enum(["minimal-pptx", "minimal-docx", "minimal-pdf", "minimal-png"])
4193
- }).strict().optional()
4194
- }).strict()
4195
- ).min(1)
4196
- }).strict()
4197
- ]);
4198
- var CraftbookTestFixtureFileSchema = z19.object({
4199
- path: z19.string().min(1),
4200
- content: z19.string(),
4201
- /** Defaults to `workspace`; `harness` never enters the model-visible project. */
4202
- surface: z19.enum(["workspace", "artifact", "harness"]).optional(),
4203
- /**
4204
- * Whether the fixture is presented to the model as source material.
4205
- * Defaults to true; false still seeds the file for browsers and graders.
4206
- */
4207
- modelInput: z19.boolean().optional()
4208
- }).strict();
4209
- var CraftbookTestWorkerSchema = z19.object({
4210
- name: z19.string().min(1),
4211
- role: z19.string().min(1),
4212
- description: z19.string().optional(),
4213
- about: z19.string().optional()
4214
- }).strict();
4215
- var CraftbookTestSetupSchema = z19.object({
4216
- projectName: z19.string().min(1),
4217
- about: z19.string().optional(),
4218
- missionObjectives: z19.string().optional(),
4219
- files: z19.array(CraftbookTestFixtureFileSchema).default([]),
4220
- /** Exact values supplied to the catalog craftbook's `paramSchema`. */
4221
- craftbookParams: z19.record(z19.string(), z19.string()).optional(),
4222
- /**
4223
- * Direct execution target. When present the harness seeds this gezel
4224
- * and sends the kickoff straight to it (measuring whether the book
4225
- * guides the work); absent → the Meester routes.
4226
- */
4227
- worker: CraftbookTestWorkerSchema.optional()
4228
- }).strict();
4229
- var CraftbookTestDeliverableSchema = z19.object({
4230
- path: z19.string().min(1),
4231
- kind: DeliverableKindSchema,
4232
- minBytes: z19.number().int().positive().optional(),
4233
- checks: z19.array(CraftbookTestCheckSchema).optional()
4234
- }).strict();
4235
- var CraftbookTestMockExpectationSchema = z19.object({
4236
- /** Mock service id from `mocks[]`. */
4237
- service: MockServiceIdSchema,
4238
- minRequests: z19.number().int().positive().optional(),
4239
- /** Regex sources matched against logged request paths. */
4240
- requiredPaths: z19.array(z19.string().min(1)).optional(),
4241
- forbiddenPaths: z19.array(z19.string().min(1)).optional(),
4242
- /**
4243
- * Exact MCP tool names that must each have been called at least once
4244
- * on this service (`kind: 'mcp'` only). Exact names, not regexes —
4245
- * the tool roster is fully declared in the same file, so a pattern
4246
- * buys nothing and invites drift. Cross-checked against the mock's
4247
- * declared `tools[]` at parse time.
4248
- */
4249
- requiredTools: z19.array(z19.string().min(1)).optional(),
4250
- /** Per-MCP-tool call budgets for repeated journeys or retries. */
4251
- toolCalls: z19.record(
4252
- z19.string().min(1),
4253
- z19.object({
4254
- minCalls: z19.number().int().nonnegative().default(1),
4255
- maxCalls: z19.number().int().nonnegative().optional()
4256
- }).strict().refine(
4257
- (value) => value.maxCalls === void 0 || value.minCalls <= value.maxCalls,
4258
- "minCalls must be less than or equal to maxCalls"
4259
- )
4260
- ).optional()
4261
- }).strict();
4262
- var CraftbookTestHistoryExpectationSchema = z19.object({
4263
- kind: HistoryEventKindSchema,
4264
- minEntries: z19.number().int().nonnegative().default(1),
4265
- maxEntries: z19.number().int().nonnegative().optional(),
4266
- summaryPattern: z19.string().min(1).optional(),
4267
- flags: z19.string().optional(),
4268
- details: z19.record(z19.string(), z19.union([z19.string(), z19.number(), z19.boolean(), z19.null()])).optional()
4269
- }).strict();
4270
- var CraftbookTestSuccessSchema = z19.object({
4271
- summary: z19.string().min(1),
4272
- deliverables: z19.array(CraftbookTestDeliverableSchema).optional(),
4273
- checks: z19.array(CraftbookTestCheckSchema).optional(),
4274
- taskNotes: z19.object({
4275
- minBytes: z19.number().int().positive().optional(),
4276
- checks: z19.array(CraftbookTestCheckSchema).optional(),
4277
- requireCraftbookTask: z19.boolean().optional()
4278
- }).strict().optional(),
4279
- taskGraph: z19.object({
4280
- checks: z19.array(CraftbookTestCheckSchema).optional(),
4281
- requireCraftbookTask: z19.boolean().optional(),
4282
- /** Require the matching task to reach a terminal step (or complete). */
4283
- requireTerminalStep: z19.boolean().optional(),
4284
- requireDraftRef: z19.boolean().optional(),
4285
- draft: z19.object({
4286
- status: z19.enum(["draft", "paused", "active", "complete", "canceled"]).optional(),
4287
- minDescriptionBytes: z19.number().int().positive().optional(),
4288
- minOutcomes: z19.number().int().positive().optional(),
4289
- minSteps: z19.number().int().positive().optional(),
4290
- requireTerminalVerification: z19.boolean().optional(),
4291
- requireGatedBuildSteps: z19.boolean().optional()
4292
- }).strict().optional()
4293
- }).strict().optional(),
4294
- /** Assertions evaluated against the live mock server's request log. */
4295
- mocks: z19.array(CraftbookTestMockExpectationSchema).optional(),
4296
- /** Assertions evaluated against the project's append-only History log. */
4297
- history: z19.array(CraftbookTestHistoryExpectationSchema).optional(),
4298
- /** Workspace fixtures whose final content must equal the seeded bytes exactly. */
4299
- unchangedFixtures: z19.array(z19.string().min(1)).optional()
4300
- }).strict();
4301
- var CraftbookTestRubricSchema = z19.object({
4302
- artifact: z19.object({
4303
- /** Workspace or artifact path the judge reads (adapter derives the basename). */
4304
- path: z19.string().min(1),
4305
- kind: z19.enum(["html", "markdown", "yaml", "typescript", "json", "text"])
4306
- }).strict(),
4307
- axes: z19.array(z19.object({ name: z19.string().min(1), description: z19.string().min(1) }).strict()).min(1),
4308
- contextNote: z19.string().optional()
4309
- }).strict();
4310
- var CraftbookTestSpecSchema = z19.object({
4311
- schemaVersion: z19.literal(CRAFTBOOK_TEST_SCHEMA_VERSION),
4312
- title: z19.string().min(1),
4313
- objective: z19.string().min(1),
4314
- /**
4315
- * Task-class taxonomy tags (e.g. `html-game`, `corpus`, `external`).
4316
- * The single declared source for harness selection and batch
4317
- * planning — replaces the old regex classifiers.
4318
- */
4319
- tags: z19.array(z19.string().min(1)).default([]),
4320
- /** Kickoff chat message the harness sends. Required — every book runs. */
4321
- prompt: z19.string().min(1),
4322
- setup: CraftbookTestSetupSchema,
4323
- mocks: z19.array(MockServiceSchema).default([]),
4324
- success: CraftbookTestSuccessSchema,
4325
- rubric: CraftbookTestRubricSchema,
4326
- qualityFocus: z19.array(z19.string().min(1)).default([]),
4327
- /**
4328
- * Sanctioned escape hatch for experiments — carried opaquely, never
4329
- * interpreted by CI. Promote a field out of here before relying on it.
4330
- */
4331
- extensions: z19.record(z19.string(), z19.unknown()).optional()
4332
- }).strict().superRefine((spec, ctx) => {
4333
- const mockIds = new Set(spec.mocks.map((m) => m.id));
4334
- const mockById = new Map(spec.mocks.map((m) => [m.id, m]));
4335
- for (const [i, expectation] of (spec.success.mocks ?? []).entries()) {
4336
- if (!mockIds.has(expectation.service)) {
4337
- ctx.addIssue({
4338
- code: z19.ZodIssueCode.custom,
4339
- path: ["success", "mocks", i, "service"],
4340
- message: `success.mocks[${i}] references unknown mock service "${expectation.service}"`
4341
- });
4342
- }
4343
- if (expectation.requiredTools && expectation.requiredTools.length > 0) {
4344
- const target = mockById.get(expectation.service);
4345
- if (target && target.kind !== "mcp") {
4346
- ctx.addIssue({
4347
- code: z19.ZodIssueCode.custom,
4348
- path: ["success", "mocks", i, "requiredTools"],
4349
- message: `success.mocks[${i}].requiredTools requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4350
- });
4351
- } else if (target?.kind === "mcp") {
4352
- const declared = new Set(target.tools.map((tool) => tool.name));
4353
- for (const name of expectation.requiredTools) {
4354
- if (!declared.has(name)) {
4355
- ctx.addIssue({
4356
- code: z19.ZodIssueCode.custom,
4357
- path: ["success", "mocks", i, "requiredTools"],
4358
- message: `success.mocks[${i}].requiredTools names undeclared tool "${name}" on mcp service "${expectation.service}"`
4359
- });
4360
- }
4361
- }
4362
- }
4363
- }
4364
- if (expectation.toolCalls && Object.keys(expectation.toolCalls).length > 0) {
4365
- const target = mockById.get(expectation.service);
4366
- if (target && target.kind !== "mcp") {
4367
- ctx.addIssue({
4368
- code: z19.ZodIssueCode.custom,
4369
- path: ["success", "mocks", i, "toolCalls"],
4370
- message: `success.mocks[${i}].toolCalls requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4371
- });
4372
- } else if (target?.kind === "mcp") {
4373
- const declared = new Set(target.tools.map((tool) => tool.name));
4374
- for (const name of Object.keys(expectation.toolCalls)) {
4375
- if (!declared.has(name)) {
4376
- ctx.addIssue({
4377
- code: z19.ZodIssueCode.custom,
4378
- path: ["success", "mocks", i, "toolCalls", name],
4379
- message: `success.mocks[${i}].toolCalls names undeclared tool "${name}" on mcp service "${expectation.service}"`
4380
- });
4381
- }
4382
- }
4383
- }
4384
- }
4385
- }
4386
- for (const [i, mock] of spec.mocks.entries()) {
4387
- if (mock.kind === "http" && mock.credential.name !== `mock.${mock.id}`) {
4388
- ctx.addIssue({
4389
- code: z19.ZodIssueCode.custom,
4390
- path: ["mocks", i, "credential", "name"],
4391
- message: `http mock "${mock.id}" must use credential name "mock.${mock.id}"`
4392
- });
4393
- }
4394
- }
4395
- const workspaceFixtures = new Set(
4396
- spec.setup.files.filter((file) => file.surface === void 0 || file.surface === "workspace").map((file) => file.path)
4397
- );
4398
- for (const [i, path] of (spec.success.unchangedFixtures ?? []).entries()) {
4399
- if (!workspaceFixtures.has(path)) {
4400
- ctx.addIssue({
4401
- code: z19.ZodIssueCode.custom,
4402
- path: ["success", "unchangedFixtures", i],
4403
- message: `unchanged fixture "${path}" is not a seeded workspace file`
4404
- });
4405
- }
4406
- }
4407
- for (const [i, expectation] of (spec.success.history ?? []).entries()) {
4408
- if (expectation.maxEntries !== void 0 && expectation.minEntries > expectation.maxEntries) {
4409
- ctx.addIssue({
4410
- code: z19.ZodIssueCode.custom,
4411
- path: ["success", "history", i],
4412
- message: "minEntries must be less than or equal to maxEntries"
4413
- });
4414
- }
4415
- }
4416
- });
4417
- function parseCraftbookTestSpec(raw, opts) {
4418
- const mode = opts?.mode ?? "strict";
4419
- const candidate = mode === "tolerant" ? deepStripUnknown(raw) : raw;
4420
- const parsed = CraftbookTestSpecSchema.safeParse(candidate);
4421
- if (parsed.success) return { ok: true, spec: parsed.data };
4422
- return {
4423
- ok: false,
4424
- errors: parsed.error.issues.map((issue) => {
4425
- const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
4426
- return `${path}: ${issue.message}`;
4427
- })
4428
- };
4429
- }
4430
- function deepStripUnknown(raw) {
4431
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
4432
- const record = structuredClone(raw);
4433
- const version = record.schemaVersion;
4434
- if (typeof version === "number" && Number.isInteger(version) && version > CRAFTBOOK_TEST_SCHEMA_VERSION) {
4435
- record.schemaVersion = CRAFTBOOK_TEST_SCHEMA_VERSION;
4436
- }
4437
- for (let pass = 0; pass < 16; pass++) {
4438
- const attempt = CraftbookTestSpecSchema.safeParse(record);
4439
- if (attempt.success) return record;
4440
- const unknownKeyIssues = attempt.error.issues.filter(
4441
- (issue) => issue.code === z19.ZodIssueCode.unrecognized_keys
4442
- );
4443
- if (unknownKeyIssues.length === 0) return record;
4444
- for (const issue of unknownKeyIssues) {
4445
- const target = resolvePath(record, issue.path);
4446
- if (target && typeof target === "object" && !Array.isArray(target)) {
4447
- for (const key of issue.keys) delete target[key];
4448
- }
4449
- }
4450
- }
4451
- return record;
4452
- }
4453
- function resolvePath(root, path) {
4454
- let node = root;
4455
- for (const segment of path) {
4456
- if (node === null || typeof node !== "object") return void 0;
4457
- node = node[segment];
4458
- }
4459
- return node;
4460
- }
4461
-
4462
- // src/schemas/codex-setup.ts
4463
- import { z as z20 } from "zod";
4464
- var CodexSetupModelOptionSchema = z20.object({
4465
- id: z20.string().min(1),
4466
- label: z20.string().min(1),
4467
- description: z20.string().optional(),
4468
- kind: z20.enum(["gezel", "model"]).default("model"),
4469
- provider: z20.string().min(1),
4470
- /** Stable gezel id for persona-backed entries. Absent on raw-model entries. */
4471
- gezelId: z20.string().min(1).optional(),
4472
- role: z20.string().min(1).optional(),
4473
- /** Human-readable name of the effective inference model behind a gezel. */
4474
- modelLabel: z20.string().min(1).optional(),
4475
- contextWindow: z20.number().int().positive().optional(),
4476
- supportsReasoning: z20.boolean().optional(),
4477
- supportsTools: z20.boolean().optional()
4478
- });
4479
- var CodexSetupStateSchema = z20.enum([
4480
- "not-configured",
4481
- "configured",
4482
- "update-needed",
4483
- "conflict",
4484
- "unavailable"
4485
- ]);
4486
- var CodexSetupStatusResponseSchema = z20.object({
4487
- state: CodexSetupStateSchema,
4488
- models: z20.array(CodexSetupModelOptionSchema),
4489
- configuredModel: z20.string().optional(),
4490
- recommendedModel: z20.string().optional(),
4491
- reasons: z20.array(z20.string()),
4492
- message: z20.string().optional(),
4493
- codexInstalled: z20.boolean(),
4494
- codexVersion: z20.string().optional(),
4495
- codexPath: z20.string().optional(),
4496
- endpointsEnabled: z20.boolean(),
4497
- profileName: z20.string().min(1),
4498
- profilePath: z20.string().min(1),
4499
- launchCommand: z20.string().min(1),
4500
- bridge: z20.object({
4501
- baseUrl: z20.string().url(),
4502
- listening: z20.boolean(),
4503
- port: z20.number().int().nonnegative()
4504
- }),
4505
- canConfigure: z20.boolean(),
4506
- /** Whether Gezel-owned credential/state material exists and can be safely removed. */
4507
- canRemove: z20.boolean()
4508
- });
4509
- var ConfigureCodexRequestSchema = z20.object({
4510
- model: z20.string().min(1)
4511
- });
4512
-
4513
4131
  // src/schemas/project.ts
4514
- import { z as z21 } from "zod";
4515
- var HttpsOriginSchema = z21.string().url().refine(
4132
+ import { z as z19 } from "zod";
4133
+ var HttpsOriginSchema = z19.string().url().refine(
4516
4134
  (value) => {
4517
4135
  try {
4518
4136
  const url = new URL(value);
@@ -4523,7 +4141,7 @@ var HttpsOriginSchema = z21.string().url().refine(
4523
4141
  },
4524
4142
  { message: "must be an exact HTTPS origin (for example https://api.example.com)" }
4525
4143
  );
4526
- var ProjectGitHubSchema = z21.object({
4144
+ var ProjectGitHubSchema = z19.object({
4527
4145
  // Accept any non-empty string. Git URLs come in many forms beyond
4528
4146
  // `https://`: ssh shorthand (`git@github.com:owner/repo`), local
4529
4147
  // filesystem paths (`/path/to/bare.git`), other-host references.
@@ -4531,89 +4149,89 @@ var ProjectGitHubSchema = z21.object({
4531
4149
  // and broke Phase-3 worktree tests that use a local bare repo as
4532
4150
  // the upstream. Higher layers (the github sync code) parse the URL
4533
4151
  // and reject anything that doesn't shape up as a clonable ref.
4534
- url: z21.string().min(1),
4535
- branch: z21.string().optional(),
4152
+ url: z19.string().min(1),
4153
+ branch: z19.string().optional(),
4536
4154
  /** Resolved absolute path to the working tree. Managed by the service. */
4537
- checkoutDir: z21.string().optional(),
4538
- lastSyncedAt: z21.string().optional(),
4155
+ checkoutDir: z19.string().optional(),
4156
+ lastSyncedAt: z19.string().optional(),
4539
4157
  /** Repo default branch (e.g. "main"), detected lazily and cached. Managed by the service. */
4540
- defaultBranch: z21.string().optional()
4158
+ defaultBranch: z19.string().optional()
4541
4159
  });
4542
- var ProjectConnectorBindingSchema = z21.object({
4160
+ var ProjectConnectorBindingSchema = z19.object({
4543
4161
  /** Stable binding id; also the SecretStore `fieldId` + corpus-slug seed. */
4544
- id: z21.string().min(1),
4162
+ id: z19.string().min(1),
4545
4163
  /** The connector-type catalog id, e.g. `mail-gmail`, `linear-issues`. */
4546
- type: z21.string().min(1),
4164
+ type: z19.string().min(1),
4547
4165
  /** Catalog source the type resolved from (provenance/pin). */
4548
- sourceId: z21.string().optional(),
4166
+ sourceId: z19.string().optional(),
4549
4167
  /** Pinned connector-type version. */
4550
- version: z21.string().optional(),
4551
- displayName: z21.string().optional(),
4168
+ version: z19.string().optional(),
4169
+ displayName: z19.string().optional(),
4552
4170
  /**
4553
4171
  * Artifact-relative corpus root (`data/<corpusName>`), resolved once at bind
4554
4172
  * time and never recomputed — renaming a binding must not strand its corpus.
4555
4173
  */
4556
- corpusDir: z21.string().optional(),
4174
+ corpusDir: z19.string().optional(),
4557
4175
  /** Per-binding config, validated at bind time against the type's `configSchema`. */
4558
- config: z21.record(z21.string(), z21.unknown()).default({}),
4176
+ config: z19.record(z19.string(), z19.unknown()).default({}),
4559
4177
  /** Opaque, adapter-shaped incremental-sync cursor. Persisted so resync resumes. */
4560
- cursor: z21.unknown().optional(),
4178
+ cursor: z19.unknown().optional(),
4561
4179
  /** Pause syncing without unbinding. */
4562
- disabled: z21.boolean().optional(),
4563
- lastSyncedAt: z21.string().optional(),
4180
+ disabled: z19.boolean().optional(),
4181
+ lastSyncedAt: z19.string().optional(),
4564
4182
  /** Last sync error, surfaced in the UI; cleared on the next success. */
4565
- lastError: z21.string().optional()
4183
+ lastError: z19.string().optional()
4566
4184
  });
4567
- var ProjectNudgeConfigSchema = z21.object({
4568
- enabled: z21.boolean().optional(),
4569
- rapidIntervalMs: z21.number().int().positive().optional(),
4570
- slowIntervalMs: z21.number().int().positive().optional(),
4571
- recentActivityWindowMs: z21.number().int().positive().optional(),
4572
- rapidAttemptsBeforeBackoff: z21.number().int().positive().optional(),
4185
+ var ProjectNudgeConfigSchema = z19.object({
4186
+ enabled: z19.boolean().optional(),
4187
+ rapidIntervalMs: z19.number().int().positive().optional(),
4188
+ slowIntervalMs: z19.number().int().positive().optional(),
4189
+ recentActivityWindowMs: z19.number().int().positive().optional(),
4190
+ rapidAttemptsBeforeBackoff: z19.number().int().positive().optional(),
4573
4191
  /**
4574
4192
  * Grace period applied to the very first nudge a project ever
4575
4193
  * receives, measured from `project.createdAt`. Default per tempo;
4576
4194
  * setting `0` opts out (legacy behavior — first nudge fires as
4577
4195
  * soon as the rapid interval allows).
4578
4196
  */
4579
- firstNudgeGraceMs: z21.number().int().nonnegative().optional()
4197
+ firstNudgeGraceMs: z19.number().int().nonnegative().optional()
4580
4198
  });
4581
- var ProjectNudgeStateSchema = z21.object({
4582
- lastNudgedAt: z21.string().optional(),
4583
- consecutiveRapidNudges: z21.number().int().nonnegative().optional()
4199
+ var ProjectNudgeStateSchema = z19.object({
4200
+ lastNudgedAt: z19.string().optional(),
4201
+ consecutiveRapidNudges: z19.number().int().nonnegative().optional()
4584
4202
  });
4585
- var ProjectTabVisibilitySchema = z21.object({
4586
- overview: z21.boolean().optional(),
4587
- tasks: z21.boolean().optional(),
4588
- approvals: z21.boolean().optional(),
4589
- workspace: z21.boolean().optional(),
4590
- artifacts: z21.boolean().optional(),
4591
- map: z21.boolean().optional()
4203
+ var ProjectTabVisibilitySchema = z19.object({
4204
+ overview: z19.boolean().optional(),
4205
+ tasks: z19.boolean().optional(),
4206
+ approvals: z19.boolean().optional(),
4207
+ workspace: z19.boolean().optional(),
4208
+ artifacts: z19.boolean().optional(),
4209
+ map: z19.boolean().optional()
4592
4210
  }).strict();
4593
- var ProjectManagedWorkspaceWritePolicySchema = z21.enum(["auto", "allow", "deny"]);
4594
- var ProjectTypeProvenanceSchema = z21.object({
4211
+ var ProjectManagedWorkspaceWritePolicySchema = z19.enum(["auto", "allow", "deny"]);
4212
+ var ProjectTypeProvenanceSchema = z19.object({
4595
4213
  /** Catalog id of the applied project type. */
4596
- id: z21.string(),
4214
+ id: z19.string(),
4597
4215
  /** Type version installed at adoption. */
4598
- version: z21.string(),
4216
+ version: z19.string(),
4599
4217
  /** Catalog source the type resolved from (`bundled` | `local` | `community` | …). */
4600
- source: z21.string(),
4218
+ source: z19.string(),
4601
4219
  /** Param values collected at adoption, substituted into templates + seed files. */
4602
- params: z21.record(z21.string(), z21.unknown()).optional(),
4220
+ params: z19.record(z19.string(), z19.unknown()).optional(),
4603
4221
  /** ISO timestamp of adoption. */
4604
- appliedAt: z21.string()
4222
+ appliedAt: z19.string()
4605
4223
  });
4606
- var ProjectSchema = z21.object({
4224
+ var ProjectSchema = z19.object({
4607
4225
  id: EntityIdSchema,
4608
- name: z21.string(),
4609
- description: z21.string().optional(),
4610
- workingDir: z21.string().optional(),
4226
+ name: z19.string(),
4227
+ description: z19.string().optional(),
4228
+ workingDir: z19.string().optional(),
4611
4229
  /** Optional gezel that acts as the project's voorman (foreman). Surfaces in
4612
4230
  * the project detail pane, flows into the system prompt when a session is
4613
4231
  * scoped here. For solo projects (`mode === 'solo'`) this same field
4614
- * holds the project's ambachtsman — the data is unchanged, only the
4232
+ * holds the project's Builder — the data is unchanged, only the
4615
4233
  * label flips. */
4616
- voormanGezelId: z21.string().optional(),
4234
+ voormanGezelId: z19.string().optional(),
4617
4235
  /**
4618
4236
  * Internal marker: ISO timestamp of the one-time automatic voorman
4619
4237
  * assignment. The indexer ensures every project ends up with a voorman
@@ -4623,7 +4241,7 @@ var ProjectSchema = z21.object({
4623
4241
  * silently re-populated on the next scan. Not user-editable; absent on
4624
4242
  * projects last written before this field existed.
4625
4243
  */
4626
- voormanAutoAssignedAt: z21.string().optional(),
4244
+ voormanAutoAssignedAt: z19.string().optional(),
4627
4245
  /**
4628
4246
  * Roster — gezels that have been pulled into this project. Populated
4629
4247
  * automatically the first time a gezel is set as voorman, opens a
@@ -4638,7 +4256,7 @@ var ProjectSchema = z21.object({
4638
4256
  * (back-compat with every project written before this field
4639
4257
  * existed).
4640
4258
  */
4641
- gezelIds: z21.array(z21.string()).optional(),
4259
+ gezelIds: z19.array(z19.string()).optional(),
4642
4260
  /**
4643
4261
  * Suggested-work keys the user has dismissed ("don't offer this again
4644
4262
  * here"). Advisory UI state, same spirit as `gezelIds`: enabling a
@@ -4648,7 +4266,7 @@ var ProjectSchema = z21.object({
4648
4266
  * `project-type:<typeId>:<scheduleKey>`). Deliberately not exposed
4649
4267
  * through the model-facing `update_project` MCP tool.
4650
4268
  */
4651
- suggestedWorkDismissed: z21.array(z21.string()).optional(),
4269
+ suggestedWorkDismissed: z19.array(z19.string()).optional(),
4652
4270
  /**
4653
4271
  * Shared per-project configuration values ("project properties") that
4654
4272
  * craftbook params and features draw from — e.g. `content.language`,
@@ -4657,26 +4275,26 @@ var ProjectSchema = z21.object({
4657
4275
  * ids are allowed (the registry improves display, it doesn't gate).
4658
4276
  * Values are plain strings; empty string is treated as unset.
4659
4277
  */
4660
- properties: z21.record(z21.string(), z21.string()).optional(),
4278
+ properties: z19.record(z19.string(), z19.string()).optional(),
4661
4279
  /**
4662
4280
  * Project shape. `crew` (the default) is the original behavior — the
4663
4281
  * voorman recruits and coordinates a team of specialists. `solo` is a
4664
- * "job" — a single specialist (the ambachtsman, stored in
4282
+ * "job" — a single Builder (stored in
4665
4283
  * `voormanGezelId`) handles the whole project themselves; team-
4666
4284
  * management MCP tools are stripped from their session, and the
4667
4285
  * Meester is instructed not to nominate other gezels. Missing → `crew`
4668
4286
  * for back-compat with every project on disk before this field
4669
4287
  * existed.
4670
4288
  */
4671
- mode: z21.enum(["crew", "solo"]).optional(),
4289
+ mode: z19.enum(["crew", "solo"]).optional(),
4672
4290
  /**
4673
4291
  * Optional custom label for this project's lead gezel, overriding the
4674
- * mode-based default ("Voorman" / "Ambachtsman") everywhere the UI
4292
+ * mode-based default ("Voorman" / "Builder") everywhere the UI
4675
4293
  * renders it. Set by a project type at adoption (e.g. checkers →
4676
4294
  * "Opponent"); absent → the mode default. The data field stays
4677
4295
  * `voormanGezelId` — only the label changes.
4678
4296
  */
4679
- leadLabel: z21.string().optional(),
4297
+ leadLabel: z19.string().optional(),
4680
4298
  /**
4681
4299
  * Lean-agent profile (set by a project type at adoption, e.g. checkers).
4682
4300
  * When true, sessions here get a minimal tool surface (the type's script
@@ -4684,7 +4302,7 @@ var ProjectSchema = z21.object({
4684
4302
  * scaffolding). Keeps small local models from being overwhelmed on a
4685
4303
  * focused single-purpose task. Absent → the full agent profile.
4686
4304
  */
4687
- leanProfile: z21.boolean().optional(),
4305
+ leanProfile: z19.boolean().optional(),
4688
4306
  /**
4689
4307
  * Per-project workspace-indexing switch. Missing/true preserves the
4690
4308
  * historical behavior: structural discovery plus the content-index refresh
@@ -4697,9 +4315,9 @@ var ProjectSchema = z21.object({
4697
4315
  * document index. Project-type manifests may seed the value at adoption and
4698
4316
  * the user can override it later in Project Settings.
4699
4317
  */
4700
- indexingEnabled: z21.boolean().optional(),
4318
+ indexingEnabled: z19.boolean().optional(),
4701
4319
  github: ProjectGitHubSchema.optional(),
4702
- connectors: z21.array(ProjectConnectorBindingSchema).optional(),
4320
+ connectors: z19.array(ProjectConnectorBindingSchema).optional(),
4703
4321
  nudgeConfig: ProjectNudgeConfigSchema.optional(),
4704
4322
  nudgeState: ProjectNudgeStateSchema.optional(),
4705
4323
  /**
@@ -4722,7 +4340,7 @@ var ProjectSchema = z21.object({
4722
4340
  * @deprecated Use `managedWorkspaceWritePolicy` and the centralized
4723
4341
  * `projectManagedWorkspaceWritable` resolver.
4724
4342
  */
4725
- allowGezelWrites: z21.boolean().optional(),
4343
+ allowGezelWrites: z19.boolean().optional(),
4726
4344
  /**
4727
4345
  * Per-project Codex execution posture selected from the project status bar.
4728
4346
  * It overrides per-gezel/install Codex defaults so the visible control is
@@ -4759,7 +4377,7 @@ var ProjectSchema = z21.object({
4759
4377
  * weak local model having to take an explicit action. Reversible and
4760
4378
  * non-destructive; chat and direct tool calls keep working.
4761
4379
  */
4762
- status: z21.enum(["active", "readonly", "inactive", "stable"]).optional(),
4380
+ status: z19.enum(["active", "readonly", "inactive", "stable"]).optional(),
4763
4381
  /**
4764
4382
  * Bury this project in the navigation without deleting it. Archived
4765
4383
  * projects remain available from the dedicated section in the full
@@ -4769,13 +4387,13 @@ var ProjectSchema = z21.object({
4769
4387
  * Missing/false means visible in the ordinary project UX, preserving
4770
4388
  * compatibility with projects written before archiving existed.
4771
4389
  */
4772
- archived: z21.boolean().optional(),
4390
+ archived: z19.boolean().optional(),
4773
4391
  /**
4774
4392
  * Per-project override of the `run_nodejs_script` wall-clock
4775
4393
  * timeout. Clamped between 30 seconds and 30 minutes. Missing →
4776
4394
  * the service-side default (5 min) applies.
4777
4395
  */
4778
- workspaceScriptTimeoutMs: z21.number().int().min(3e4).max(30 * 6e4).optional(),
4396
+ workspaceScriptTimeoutMs: z19.number().int().min(3e4).max(30 * 6e4).optional(),
4779
4397
  /**
4780
4398
  * Named credentials this project is explicitly allowed to use.
4781
4399
  * Credentials are stored once globally in `SecretStore`; a grant
@@ -4784,76 +4402,515 @@ var ProjectSchema = z21.object({
4784
4402
  * Missing → no credentials granted. See `scripts/dispatcher.ts`
4785
4403
  * and `secrets/registry.ts` for resolution.
4786
4404
  */
4787
- grantedCredentials: z21.array(z21.string()).optional(),
4405
+ grantedCredentials: z19.array(z19.string()).optional(),
4406
+ /**
4407
+ * Advanced exact-origin bindings for toolset credentials. Built-in provider
4408
+ * credentials are service-pinned and webhook credentials follow the
4409
+ * configured webhook URL, so entries for those names are ignored.
4410
+ */
4411
+ credentialAllowedOrigins: z19.record(z19.string(), z19.array(HttpsOriginSchema)).optional(),
4412
+ /**
4413
+ * Explicit user override of the project's type (an id from the bundled
4414
+ * project-type taxonomy, see `project-types/taxonomy.ts`). When set, it
4415
+ * wins over `detectedProjectType` for craftbook suggestions. Cleared
4416
+ * (unset) → fall back to auto-detection. Missing on every project written
4417
+ * before this field existed.
4418
+ */
4419
+ projectTypeId: z19.string().optional(),
4420
+ /**
4421
+ * Auto-detected project type, recomputed on each content-index scan from
4422
+ * the workspace file mix + about/mission text. Not user-editable — the
4423
+ * user expresses an override via `projectTypeId`. Absent until the first
4424
+ * scan classifies the project (or when nothing scores above the floor).
4425
+ */
4426
+ detectedProjectType: z19.object({
4427
+ id: z19.string(),
4428
+ score: z19.number(),
4429
+ scannedAt: z19.string()
4430
+ }).optional(),
4431
+ /**
4432
+ * Provenance of an applied custom project type (see docs/project-types.md).
4433
+ * Distinct from `projectTypeId`, which is the taxonomy id used for craftbook
4434
+ * suggestion: a custom type stamps this on adoption and, when it `extends` a
4435
+ * taxonomy id, ALSO sets `projectTypeId` so detection-based suggestion
4436
+ * inherits. Absent on projects created without a custom type.
4437
+ */
4438
+ projectType: ProjectTypeProvenanceSchema.optional(),
4439
+ /**
4440
+ * Filesystem ownership boundary. Missing/`user` is ordinary per-account
4441
+ * state. `machine-shared` is a grandfathered project mounted from the
4442
+ * installer-managed shared root and operated on by this user's daemon.
4443
+ * The engine broker never opens the project or receives its paths.
4444
+ */
4445
+ storageScope: z19.enum(["user", "machine-shared"]).optional(),
4446
+ createdAt: z19.string(),
4447
+ updatedAt: z19.string()
4448
+ });
4449
+ function resolveProjectTypeId(project) {
4450
+ return project.projectTypeId ?? project.detectedProjectType?.id;
4451
+ }
4452
+ function projectAllowsAmbientWork(project) {
4453
+ const status = project.status ?? "active";
4454
+ return status === "active";
4455
+ }
4456
+ var InstalledPackageSchema = z19.object({
4457
+ name: z19.string(),
4458
+ version: z19.string()
4459
+ });
4460
+ var ProjectDetailSchema = ProjectSchema.extend({
4461
+ packages: z19.array(InstalledPackageSchema),
4462
+ /** Contents of `documents/about.md` inside the project, if present. */
4463
+ about: z19.string().optional(),
4464
+ /** Contents of `documents/missionObjectives.md`, if present. */
4465
+ missionObjectives: z19.string().optional()
4466
+ });
4467
+ var ProjectFileEntrySchema = z19.object({
4468
+ name: z19.string(),
4469
+ path: z19.string(),
4470
+ isDirectory: z19.boolean(),
4471
+ /** File mtime (ms epoch). Populated only when the caller opts into stats. */
4472
+ mtimeMs: z19.number().optional()
4473
+ });
4474
+ var ProjectGithubSchema = ProjectGitHubSchema;
4475
+
4476
+ // src/schemas/craftbook-test.ts
4477
+ var CRAFTBOOK_TEST_SCHEMA_VERSION = 1;
4478
+ var CRAFTBOOK_TEST_FILENAME = "test.json";
4479
+ var PrometheusAlertsCheckSchema = z20.object({
4480
+ kind: z20.literal("prometheusAlerts"),
4481
+ file: z20.string().min(1),
4482
+ minRules: z20.number().int().positive().optional(),
4483
+ maxPageAlerts: z20.number().int().nonnegative().optional(),
4484
+ allowedSeverities: z20.array(z20.string().min(1)).optional(),
4485
+ requiredServices: z20.array(z20.string().min(1)).optional(),
4486
+ requiredRunbookUrls: z20.array(z20.string().min(1)).optional()
4487
+ }).strict();
4488
+ var NodeScriptPassesCheckSchema = z20.object({
4489
+ kind: z20.literal("nodeScriptPasses"),
4490
+ script: z20.string().min(1),
4491
+ timeoutMs: z20.number().int().positive().optional(),
4492
+ requiredOutput: z20.array(
4493
+ z20.object({
4494
+ pattern: z20.string().min(1),
4495
+ flags: z20.string().optional(),
4496
+ label: z20.string().optional()
4497
+ }).strict()
4498
+ ).optional()
4499
+ }).strict();
4500
+ var BinaryDocumentCheckSchema = z20.object({
4501
+ kind: z20.literal("binaryDocument"),
4502
+ file: z20.string().min(1),
4503
+ /** Look in the artifacts drawer instead of the workspace. */
4504
+ artifact: z20.boolean().optional(),
4505
+ /** Floor on the container's byte length; defaults to 1000. */
4506
+ minBytes: z20.number().int().positive().optional()
4507
+ }).strict();
4508
+ var CraftbookTestCheckSchema = z20.union([
4509
+ GateCheckSchema,
4510
+ PrometheusAlertsCheckSchema,
4511
+ NodeScriptPassesCheckSchema,
4512
+ BinaryDocumentCheckSchema
4513
+ ]);
4514
+ var MockServiceIdSchema = z20.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "mock service ids are lowercase kebab-case");
4515
+ var MockToolsetIdSchema = z20.string().min(1).regex(
4516
+ /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,
4517
+ "mock toolset ids are lowercase catalog ids or scoped npm-style ids"
4518
+ );
4519
+ var MockHttpRouteSchema = z20.object({
4520
+ method: z20.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
4521
+ /** Exact path, `:param` segments, or a trailing `*` wildcard. */
4522
+ path: z20.string().min(1),
4523
+ status: z20.number().int().min(100).max(599).default(200),
4524
+ headers: z20.record(z20.string(), z20.string()).optional(),
4525
+ /** String bodies are served verbatim; anything else is JSON-encoded. */
4526
+ body: z20.unknown(),
4527
+ latencyMs: z20.number().int().nonnegative().optional()
4528
+ }).strict();
4529
+ var MockServiceSchema = z20.discriminatedUnion("kind", [
4530
+ z20.object({
4531
+ kind: z20.literal("http"),
4532
+ id: MockServiceIdSchema,
4533
+ description: z20.string().min(1),
4534
+ /**
4535
+ * v1 mock HTTP is reachable ONLY through the `http.authed`
4536
+ * credential rail (anonymous script/browser HTTP hard-rejects
4537
+ * loopback), so a credential is required. The harness seeds it and
4538
+ * grants the mock's exact origin on the trial project.
4539
+ */
4540
+ credential: z20.object({
4541
+ name: z20.string().regex(/^mock\.[a-z0-9][a-z0-9.-]*$/, "mock credentials are named mock.<service-id>"),
4542
+ authScheme: z20.enum(["bearer", "basic"]).optional()
4543
+ }).strict(),
4544
+ routes: z20.array(MockHttpRouteSchema).min(1)
4545
+ }).strict(),
4546
+ z20.object({
4547
+ kind: z20.literal("webhook"),
4548
+ id: MockServiceIdSchema,
4549
+ description: z20.string().min(1),
4550
+ /** Receiver path; defaults to `/webhook` when omitted. */
4551
+ path: z20.string().min(1).optional()
4552
+ }).strict(),
4553
+ z20.object({
4554
+ kind: z20.literal("cli"),
4555
+ id: MockServiceIdSchema,
4556
+ description: z20.string().min(1),
4557
+ /** Fake-CLI shim seeded as a workspace file (today's dry-run pattern). */
4558
+ shim: z20.object({ path: z20.string().min(1), content: z20.string() }).strict()
4559
+ }).strict(),
4560
+ z20.object({
4561
+ kind: z20.literal("mcp"),
4562
+ id: MockServiceIdSchema,
4563
+ description: z20.string().min(1),
4564
+ /** Override the local-catalog id when the mock replaces a real dependency. */
4565
+ toolsetId: MockToolsetIdSchema.optional(),
4566
+ /**
4567
+ * Served live by the eval mock rail: each declared tool becomes a
4568
+ * real tool on a per-trial Streamable-HTTP MCP endpoint, installed
4569
+ * into the trial via a local-catalog `mock-mcp-<id>` toolset.
4570
+ * `resultTemplate` is JSON-encoded as the tool result text
4571
+ * (default `{"ok":true}` when absent).
4572
+ */
4573
+ tools: z20.array(
4574
+ z20.object({
4575
+ name: z20.string().min(1),
4576
+ description: z20.string().min(1),
4577
+ resultTemplate: z20.unknown().optional(),
4578
+ /** Deterministic stateful responses, consumed in call order; the last repeats. */
4579
+ resultSequence: z20.array(z20.unknown()).min(1).optional(),
4580
+ /**
4581
+ * Deterministic eval-only file materialization after this
4582
+ * tool call. The fixture must match the container the
4583
+ * deliverable's extension claims — a `minimal-pptx` written
4584
+ * to a `.docx` path now fails the `binaryDocument` check on
4585
+ * content type rather than sliding through a byte floor.
4586
+ */
4587
+ writeFixture: z20.object({
4588
+ surface: z20.enum(["workspace", "artifact"]),
4589
+ pathArgument: z20.string().min(1),
4590
+ fixture: z20.enum(["minimal-pptx", "minimal-docx", "minimal-pdf", "minimal-png"])
4591
+ }).strict().optional()
4592
+ }).strict()
4593
+ ).min(1)
4594
+ }).strict()
4595
+ ]);
4596
+ var CraftbookTestFixtureFileSchema = z20.object({
4597
+ path: z20.string().min(1),
4598
+ content: z20.string(),
4599
+ /** Defaults to `workspace`; `harness` never enters the model-visible project. */
4600
+ surface: z20.enum(["workspace", "artifact", "harness"]).optional(),
4788
4601
  /**
4789
- * Advanced exact-origin bindings for toolset credentials. Built-in provider
4790
- * credentials are service-pinned and webhook credentials follow the
4791
- * configured webhook URL, so entries for those names are ignored.
4602
+ * Whether the fixture is presented to the model as source material.
4603
+ * Defaults to true; false still seeds the file for browsers and graders.
4792
4604
  */
4793
- credentialAllowedOrigins: z21.record(z21.string(), z21.array(HttpsOriginSchema)).optional(),
4605
+ modelInput: z20.boolean().optional()
4606
+ }).strict();
4607
+ var CraftbookTestWorkerSchema = z20.object({
4608
+ name: z20.string().min(1),
4609
+ role: z20.string().min(1),
4610
+ description: z20.string().optional(),
4611
+ about: z20.string().optional()
4612
+ }).strict();
4613
+ var CraftbookTestSetupSchema = z20.object({
4614
+ projectName: z20.string().min(1),
4615
+ about: z20.string().optional(),
4616
+ missionObjectives: z20.string().optional(),
4617
+ /** Reproduce project write posture before the craftbook task starts. */
4618
+ managedWorkspaceWritePolicy: ProjectManagedWorkspaceWritePolicySchema.optional(),
4619
+ files: z20.array(CraftbookTestFixtureFileSchema).default([]),
4620
+ /** Exact values supplied to the catalog craftbook's `paramSchema`. */
4621
+ craftbookParams: z20.record(z20.string(), z20.string()).optional(),
4794
4622
  /**
4795
- * Explicit user override of the project's type (an id from the bundled
4796
- * project-type taxonomy, see `project-types/taxonomy.ts`). When set, it
4797
- * wins over `detectedProjectType` for craftbook suggestions. Cleared
4798
- * (unset) → fall back to auto-detection. Missing on every project written
4799
- * before this field existed.
4623
+ * Direct execution target. When present the harness seeds this gezel
4624
+ * and sends the kickoff straight to it (measuring whether the book
4625
+ * guides the work); absent the Meester routes.
4800
4626
  */
4801
- projectTypeId: z21.string().optional(),
4627
+ worker: CraftbookTestWorkerSchema.optional()
4628
+ }).strict();
4629
+ var CraftbookTestDeliverableSchema = z20.object({
4630
+ path: z20.string().min(1),
4631
+ kind: DeliverableKindSchema,
4632
+ /** Grade the path in the project's artifacts drawer, not its workspace. */
4633
+ artifact: z20.boolean().optional(),
4634
+ minBytes: z20.number().int().positive().optional(),
4635
+ checks: z20.array(CraftbookTestCheckSchema).optional()
4636
+ }).strict();
4637
+ var CraftbookTestMockExpectationSchema = z20.object({
4638
+ /** Mock service id from `mocks[]`. */
4639
+ service: MockServiceIdSchema,
4640
+ minRequests: z20.number().int().positive().optional(),
4641
+ /** Regex sources matched against logged request paths. */
4642
+ requiredPaths: z20.array(z20.string().min(1)).optional(),
4643
+ forbiddenPaths: z20.array(z20.string().min(1)).optional(),
4802
4644
  /**
4803
- * Auto-detected project type, recomputed on each content-index scan from
4804
- * the workspace file mix + about/mission text. Not user-editable the
4805
- * user expresses an override via `projectTypeId`. Absent until the first
4806
- * scan classifies the project (or when nothing scores above the floor).
4645
+ * Exact MCP tool names that must each have been called at least once
4646
+ * on this service (`kind: 'mcp'` only). Exact names, not regexes
4647
+ * the tool roster is fully declared in the same file, so a pattern
4648
+ * buys nothing and invites drift. Cross-checked against the mock's
4649
+ * declared `tools[]` at parse time.
4807
4650
  */
4808
- detectedProjectType: z21.object({
4809
- id: z21.string(),
4810
- score: z21.number(),
4811
- scannedAt: z21.string()
4812
- }).optional(),
4651
+ requiredTools: z20.array(z20.string().min(1)).optional(),
4652
+ /** Per-MCP-tool call budgets for repeated journeys or retries. */
4653
+ toolCalls: z20.record(
4654
+ z20.string().min(1),
4655
+ z20.object({
4656
+ minCalls: z20.number().int().nonnegative().default(1),
4657
+ maxCalls: z20.number().int().nonnegative().optional()
4658
+ }).strict().refine(
4659
+ (value) => value.maxCalls === void 0 || value.minCalls <= value.maxCalls,
4660
+ "minCalls must be less than or equal to maxCalls"
4661
+ )
4662
+ ).optional()
4663
+ }).strict();
4664
+ var CraftbookTestHistoryExpectationSchema = z20.object({
4665
+ kind: HistoryEventKindSchema,
4666
+ minEntries: z20.number().int().nonnegative().default(1),
4667
+ maxEntries: z20.number().int().nonnegative().optional(),
4668
+ summaryPattern: z20.string().min(1).optional(),
4669
+ flags: z20.string().optional(),
4670
+ details: z20.record(z20.string(), z20.union([z20.string(), z20.number(), z20.boolean(), z20.null()])).optional()
4671
+ }).strict();
4672
+ var CraftbookTestSuccessSchema = z20.object({
4673
+ summary: z20.string().min(1),
4674
+ deliverables: z20.array(CraftbookTestDeliverableSchema).optional(),
4675
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4676
+ taskNotes: z20.object({
4677
+ minBytes: z20.number().int().positive().optional(),
4678
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4679
+ requireCraftbookTask: z20.boolean().optional()
4680
+ }).strict().optional(),
4681
+ taskGraph: z20.object({
4682
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4683
+ requireCraftbookTask: z20.boolean().optional(),
4684
+ /** Require the matching task to reach a terminal step (or complete). */
4685
+ requireTerminalStep: z20.boolean().optional(),
4686
+ requireDraftRef: z20.boolean().optional(),
4687
+ draft: z20.object({
4688
+ status: z20.enum(["draft", "paused", "active", "complete", "canceled"]).optional(),
4689
+ minDescriptionBytes: z20.number().int().positive().optional(),
4690
+ minOutcomes: z20.number().int().positive().optional(),
4691
+ minSteps: z20.number().int().positive().optional(),
4692
+ requireTerminalVerification: z20.boolean().optional(),
4693
+ requireGatedBuildSteps: z20.boolean().optional()
4694
+ }).strict().optional()
4695
+ }).strict().optional(),
4696
+ /** Assertions evaluated against the live mock server's request log. */
4697
+ mocks: z20.array(CraftbookTestMockExpectationSchema).optional(),
4698
+ /** Assertions evaluated against the project's append-only History log. */
4699
+ history: z20.array(CraftbookTestHistoryExpectationSchema).optional(),
4700
+ /** Workspace fixtures whose final content must equal the seeded bytes exactly. */
4701
+ unchangedFixtures: z20.array(z20.string().min(1)).optional()
4702
+ }).strict();
4703
+ var CraftbookTestRubricSchema = z20.object({
4704
+ artifact: z20.object({
4705
+ /** Workspace or artifact path the judge reads (adapter derives the basename). */
4706
+ path: z20.string().min(1),
4707
+ kind: z20.enum(["html", "markdown", "yaml", "typescript", "json", "text"])
4708
+ }).strict(),
4709
+ axes: z20.array(z20.object({ name: z20.string().min(1), description: z20.string().min(1) }).strict()).min(1),
4710
+ contextNote: z20.string().optional()
4711
+ }).strict();
4712
+ var CraftbookTestSpecSchema = z20.object({
4713
+ schemaVersion: z20.literal(CRAFTBOOK_TEST_SCHEMA_VERSION),
4714
+ title: z20.string().min(1),
4715
+ objective: z20.string().min(1),
4813
4716
  /**
4814
- * Provenance of an applied custom project type (see docs/project-types.md).
4815
- * Distinct from `projectTypeId`, which is the taxonomy id used for craftbook
4816
- * suggestion: a custom type stamps this on adoption and, when it `extends` a
4817
- * taxonomy id, ALSO sets `projectTypeId` so detection-based suggestion
4818
- * inherits. Absent on projects created without a custom type.
4717
+ * Task-class taxonomy tags (e.g. `html-game`, `corpus`, `external`).
4718
+ * The single declared source for harness selection and batch
4719
+ * planning replaces the old regex classifiers.
4819
4720
  */
4820
- projectType: ProjectTypeProvenanceSchema.optional(),
4721
+ tags: z20.array(z20.string().min(1)).default([]),
4722
+ /** Kickoff chat message the harness sends. Required — every book runs. */
4723
+ prompt: z20.string().min(1),
4724
+ setup: CraftbookTestSetupSchema,
4725
+ mocks: z20.array(MockServiceSchema).default([]),
4726
+ success: CraftbookTestSuccessSchema,
4727
+ rubric: CraftbookTestRubricSchema,
4728
+ qualityFocus: z20.array(z20.string().min(1)).default([]),
4821
4729
  /**
4822
- * Filesystem ownership boundary. Missing/`user` is ordinary per-account
4823
- * state. `machine-shared` is a grandfathered project mounted from the
4824
- * installer-managed shared root and operated on by this user's daemon.
4825
- * The engine broker never opens the project or receives its paths.
4730
+ * Sanctioned escape hatch for experiments carried opaquely, never
4731
+ * interpreted by CI. Promote a field out of here before relying on it.
4826
4732
  */
4827
- storageScope: z21.enum(["user", "machine-shared"]).optional(),
4828
- createdAt: z21.string(),
4829
- updatedAt: z21.string()
4733
+ extensions: z20.record(z20.string(), z20.unknown()).optional()
4734
+ }).strict().superRefine((spec, ctx) => {
4735
+ const mockIds = new Set(spec.mocks.map((m) => m.id));
4736
+ const mockById = new Map(spec.mocks.map((m) => [m.id, m]));
4737
+ for (const [i, expectation] of (spec.success.mocks ?? []).entries()) {
4738
+ if (!mockIds.has(expectation.service)) {
4739
+ ctx.addIssue({
4740
+ code: z20.ZodIssueCode.custom,
4741
+ path: ["success", "mocks", i, "service"],
4742
+ message: `success.mocks[${i}] references unknown mock service "${expectation.service}"`
4743
+ });
4744
+ }
4745
+ if (expectation.requiredTools && expectation.requiredTools.length > 0) {
4746
+ const target = mockById.get(expectation.service);
4747
+ if (target && target.kind !== "mcp") {
4748
+ ctx.addIssue({
4749
+ code: z20.ZodIssueCode.custom,
4750
+ path: ["success", "mocks", i, "requiredTools"],
4751
+ message: `success.mocks[${i}].requiredTools requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4752
+ });
4753
+ } else if (target?.kind === "mcp") {
4754
+ const declared = new Set(target.tools.map((tool) => tool.name));
4755
+ for (const name of expectation.requiredTools) {
4756
+ if (!declared.has(name)) {
4757
+ ctx.addIssue({
4758
+ code: z20.ZodIssueCode.custom,
4759
+ path: ["success", "mocks", i, "requiredTools"],
4760
+ message: `success.mocks[${i}].requiredTools names undeclared tool "${name}" on mcp service "${expectation.service}"`
4761
+ });
4762
+ }
4763
+ }
4764
+ }
4765
+ }
4766
+ if (expectation.toolCalls && Object.keys(expectation.toolCalls).length > 0) {
4767
+ const target = mockById.get(expectation.service);
4768
+ if (target && target.kind !== "mcp") {
4769
+ ctx.addIssue({
4770
+ code: z20.ZodIssueCode.custom,
4771
+ path: ["success", "mocks", i, "toolCalls"],
4772
+ message: `success.mocks[${i}].toolCalls requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4773
+ });
4774
+ } else if (target?.kind === "mcp") {
4775
+ const declared = new Set(target.tools.map((tool) => tool.name));
4776
+ for (const name of Object.keys(expectation.toolCalls)) {
4777
+ if (!declared.has(name)) {
4778
+ ctx.addIssue({
4779
+ code: z20.ZodIssueCode.custom,
4780
+ path: ["success", "mocks", i, "toolCalls", name],
4781
+ message: `success.mocks[${i}].toolCalls names undeclared tool "${name}" on mcp service "${expectation.service}"`
4782
+ });
4783
+ }
4784
+ }
4785
+ }
4786
+ }
4787
+ }
4788
+ for (const [i, mock] of spec.mocks.entries()) {
4789
+ if (mock.kind === "http" && mock.credential.name !== `mock.${mock.id}`) {
4790
+ ctx.addIssue({
4791
+ code: z20.ZodIssueCode.custom,
4792
+ path: ["mocks", i, "credential", "name"],
4793
+ message: `http mock "${mock.id}" must use credential name "mock.${mock.id}"`
4794
+ });
4795
+ }
4796
+ }
4797
+ const workspaceFixtures = new Set(
4798
+ spec.setup.files.filter((file) => file.surface === void 0 || file.surface === "workspace").map((file) => file.path)
4799
+ );
4800
+ for (const [i, path] of (spec.success.unchangedFixtures ?? []).entries()) {
4801
+ if (!workspaceFixtures.has(path)) {
4802
+ ctx.addIssue({
4803
+ code: z20.ZodIssueCode.custom,
4804
+ path: ["success", "unchangedFixtures", i],
4805
+ message: `unchanged fixture "${path}" is not a seeded workspace file`
4806
+ });
4807
+ }
4808
+ }
4809
+ for (const [i, expectation] of (spec.success.history ?? []).entries()) {
4810
+ if (expectation.maxEntries !== void 0 && expectation.minEntries > expectation.maxEntries) {
4811
+ ctx.addIssue({
4812
+ code: z20.ZodIssueCode.custom,
4813
+ path: ["success", "history", i],
4814
+ message: "minEntries must be less than or equal to maxEntries"
4815
+ });
4816
+ }
4817
+ }
4830
4818
  });
4831
- function resolveProjectTypeId(project) {
4832
- return project.projectTypeId ?? project.detectedProjectType?.id;
4819
+ function parseCraftbookTestSpec(raw, opts) {
4820
+ const mode = opts?.mode ?? "strict";
4821
+ const candidate = mode === "tolerant" ? deepStripUnknown(raw) : raw;
4822
+ const parsed = CraftbookTestSpecSchema.safeParse(candidate);
4823
+ if (parsed.success) return { ok: true, spec: parsed.data };
4824
+ return {
4825
+ ok: false,
4826
+ errors: parsed.error.issues.map((issue) => {
4827
+ const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
4828
+ return `${path}: ${issue.message}`;
4829
+ })
4830
+ };
4833
4831
  }
4834
- function projectAllowsAmbientWork(project) {
4835
- const status = project.status ?? "active";
4836
- return status === "active";
4832
+ function deepStripUnknown(raw) {
4833
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
4834
+ const record = structuredClone(raw);
4835
+ const version = record.schemaVersion;
4836
+ if (typeof version === "number" && Number.isInteger(version) && version > CRAFTBOOK_TEST_SCHEMA_VERSION) {
4837
+ record.schemaVersion = CRAFTBOOK_TEST_SCHEMA_VERSION;
4838
+ }
4839
+ for (let pass = 0; pass < 16; pass++) {
4840
+ const attempt = CraftbookTestSpecSchema.safeParse(record);
4841
+ if (attempt.success) return record;
4842
+ const unknownKeyIssues = attempt.error.issues.filter(
4843
+ (issue) => issue.code === z20.ZodIssueCode.unrecognized_keys
4844
+ );
4845
+ if (unknownKeyIssues.length === 0) return record;
4846
+ for (const issue of unknownKeyIssues) {
4847
+ const target = resolvePath(record, issue.path);
4848
+ if (target && typeof target === "object" && !Array.isArray(target)) {
4849
+ for (const key of issue.keys) delete target[key];
4850
+ }
4851
+ }
4852
+ }
4853
+ return record;
4854
+ }
4855
+ function resolvePath(root, path) {
4856
+ let node = root;
4857
+ for (const segment of path) {
4858
+ if (node === null || typeof node !== "object") return void 0;
4859
+ node = node[segment];
4860
+ }
4861
+ return node;
4837
4862
  }
4838
- var InstalledPackageSchema = z21.object({
4839
- name: z21.string(),
4840
- version: z21.string()
4863
+
4864
+ // src/schemas/codex-setup.ts
4865
+ import { z as z21 } from "zod";
4866
+ var CodexSetupModelOptionSchema = z21.object({
4867
+ id: z21.string().min(1),
4868
+ label: z21.string().min(1),
4869
+ description: z21.string().optional(),
4870
+ kind: z21.enum(["gezel", "model"]).default("model"),
4871
+ provider: z21.string().min(1),
4872
+ /** Stable gezel id for persona-backed entries. Absent on raw-model entries. */
4873
+ gezelId: z21.string().min(1).optional(),
4874
+ role: z21.string().min(1).optional(),
4875
+ /** Human-readable name of the effective inference model behind a gezel. */
4876
+ modelLabel: z21.string().min(1).optional(),
4877
+ contextWindow: z21.number().int().positive().optional(),
4878
+ supportsReasoning: z21.boolean().optional(),
4879
+ supportsTools: z21.boolean().optional()
4841
4880
  });
4842
- var ProjectDetailSchema = ProjectSchema.extend({
4843
- packages: z21.array(InstalledPackageSchema),
4844
- /** Contents of `documents/about.md` inside the project, if present. */
4845
- about: z21.string().optional(),
4846
- /** Contents of `documents/missionObjectives.md`, if present. */
4847
- missionObjectives: z21.string().optional()
4881
+ var CodexSetupStateSchema = z21.enum([
4882
+ "not-configured",
4883
+ "configured",
4884
+ "update-needed",
4885
+ "conflict",
4886
+ "unavailable"
4887
+ ]);
4888
+ var CodexSetupStatusResponseSchema = z21.object({
4889
+ state: CodexSetupStateSchema,
4890
+ models: z21.array(CodexSetupModelOptionSchema),
4891
+ configuredModel: z21.string().optional(),
4892
+ recommendedModel: z21.string().optional(),
4893
+ reasons: z21.array(z21.string()),
4894
+ message: z21.string().optional(),
4895
+ codexInstalled: z21.boolean(),
4896
+ codexVersion: z21.string().optional(),
4897
+ codexPath: z21.string().optional(),
4898
+ endpointsEnabled: z21.boolean(),
4899
+ profileName: z21.string().min(1),
4900
+ profilePath: z21.string().min(1),
4901
+ launchCommand: z21.string().min(1),
4902
+ bridge: z21.object({
4903
+ baseUrl: z21.string().url(),
4904
+ listening: z21.boolean(),
4905
+ port: z21.number().int().nonnegative()
4906
+ }),
4907
+ canConfigure: z21.boolean(),
4908
+ /** Whether Gezel-owned credential/state material exists and can be safely removed. */
4909
+ canRemove: z21.boolean()
4848
4910
  });
4849
- var ProjectFileEntrySchema = z21.object({
4850
- name: z21.string(),
4851
- path: z21.string(),
4852
- isDirectory: z21.boolean(),
4853
- /** File mtime (ms epoch). Populated only when the caller opts into stats. */
4854
- mtimeMs: z21.number().optional()
4911
+ var ConfigureCodexRequestSchema = z21.object({
4912
+ model: z21.string().min(1)
4855
4913
  });
4856
- var ProjectGithubSchema = ProjectGitHubSchema;
4857
4914
 
4858
4915
  // src/schemas/project-local.ts
4859
4916
  import { z as z22 } from "zod";
@@ -5169,6 +5226,15 @@ var TaskSchema = z23.object({
5169
5226
  issueRef: z23.string().regex(/^BW-[1-9]\d*$/),
5170
5227
  path: z23.string().min(1)
5171
5228
  }),
5229
+ z23.object({
5230
+ /**
5231
+ * An invoke_craftbook call keyed to one persisted root chat turn.
5232
+ * The HTTP task boundary uses this opaque digest to return the first
5233
+ * still-active task when a provider continuation repeats the call.
5234
+ */
5235
+ kind: z23.literal("craftbook-invocation"),
5236
+ key: z23.string().regex(/^craftbook-root-v1:[a-f0-9]{64}$/)
5237
+ }),
5172
5238
  z23.object({
5173
5239
  /**
5174
5240
  * A host materialized from a gezel template's `suggestedCraftbooks`
@@ -5198,6 +5264,13 @@ var TaskSchema = z23.object({
5198
5264
  params: z23.record(z23.string(), z23.unknown()).optional(),
5199
5265
  at: z23.string()
5200
5266
  }).optional(),
5267
+ /**
5268
+ * Naming presentation inherited from the session that launched this
5269
+ * workflow. Task handoffs create fresh sessions (and may be rehydrated
5270
+ * after restart), so they cannot rely on the launcher's session record
5271
+ * still being available when the next step starts.
5272
+ */
5273
+ roleBasedNameOnlyMode: z23.boolean().optional(),
5201
5274
  createdAt: z23.string(),
5202
5275
  updatedAt: z23.string(),
5203
5276
  createdBy: TaskAssigneeSchema
@@ -5252,6 +5325,8 @@ var CreateTaskRequestSchema = z23.object({
5252
5325
  }).optional(),
5253
5326
  fanout: NewTaskFanoutSchema.optional(),
5254
5327
  createdBy: TaskAssigneeSchema.optional(),
5328
+ /** Preserve the launcher's naming presentation across task handoffs. */
5329
+ roleBasedNameOnlyMode: z23.boolean().optional(),
5255
5330
  /**
5256
5331
  * Enqueue the entry-step handoff immediately after create — the
5257
5332
  * single-channel kickoff (there is no "tell a gezel about work"
@@ -5261,7 +5336,13 @@ var CreateTaskRequestSchema = z23.object({
5261
5336
  * on cron/fanout hosts (their children dispatch via their own
5262
5337
  * activation hooks — flag-dispatching the host would double-engage).
5263
5338
  */
5264
- dispatchEntry: z23.boolean().optional()
5339
+ dispatchEntry: z23.boolean().optional(),
5340
+ /**
5341
+ * Internal invoke_craftbook idempotency digest. The task route converts
5342
+ * it into service-owned Task.origin provenance; ordinary create_task
5343
+ * callers omit it.
5344
+ */
5345
+ craftbookInvocationKey: z23.string().regex(/^craftbook-root-v1:[a-f0-9]{64}$/).optional()
5265
5346
  }).refine((v) => !!v.craftbookId !== !!(v.steps && v.steps.length > 0), {
5266
5347
  message: "exactly one of craftbookId or steps must be provided for the main craftbook",
5267
5348
  path: ["craftbookId"]
@@ -5365,6 +5446,8 @@ var UpdateTaskStepRequestSchema = z23.object({
5365
5446
  /** Step automation hooks (single ref or ordered list). `null` detaches. */
5366
5447
  onEnter: ScriptRefListSchema.nullable().optional(),
5367
5448
  onExit: ScriptRefListSchema.nullable().optional(),
5449
+ /** Required file inputs for the step. `null` clears the declaration. */
5450
+ consumes: z23.array(CraftbookStepInputSchema).min(1).nullable().optional(),
5368
5451
  /** Auto-advance contract. `null` clears it. */
5369
5452
  advanceWhen: AdvanceWhenSchema.nullable().optional(),
5370
5453
  /** The end-of-step gate (current or legacy shape). `null` clears it. */
@@ -6175,7 +6258,7 @@ var ProjectTypeCompositionShape = {
6175
6258
  tabVisibility: ProjectTabVisibilitySchema.optional(),
6176
6259
  /**
6177
6260
  * Project shape for instances of this type. `solo` marks a single-gezel
6178
- * "ambachtsman" experience (games, the chat room): the one roster gezel
6261
+ * "Builder" experience (games, the chat room): the one roster gezel
6179
6262
  * does everything, no separate overseer voorman is recruited, and the UI
6180
6263
  * collapses the crew picker to that lead. Maps onto the project's `mode`
6181
6264
  * at adoption. Omit for the default crew shape.
@@ -6183,9 +6266,9 @@ var ProjectTypeCompositionShape = {
6183
6266
  mode: z26.enum(["crew", "solo"]).optional(),
6184
6267
  /**
6185
6268
  * Custom label for this type's project lead, shown wherever the generic
6186
- * "Voorman" / "Ambachtsman" would appear (the chat chip, project
6269
+ * "Voorman" / "Builder" would appear (the chat chip, project
6187
6270
  * settings) — e.g. checkers uses "Opponent". Aimed at solo types that
6188
- * want a domain word instead of "Ambachtsman". The underlying data field
6271
+ * want a domain word instead of "Builder". The underlying data field
6189
6272
  * stays `voormanGezelId`; only the rendered label changes.
6190
6273
  */
6191
6274
  leadLabel: z26.string().min(1).optional(),
@@ -6294,6 +6377,13 @@ var ConnectorActionSchema = z26.object({
6294
6377
  /** Consent gate this action clears at commit time (e.g. `recipient-allowlist`). */
6295
6378
  consentScope: z26.string()
6296
6379
  });
6380
+ var ConnectorSetupInstructionsSchema = z26.object({
6381
+ title: z26.string().min(1),
6382
+ description: z26.string().min(1).optional(),
6383
+ steps: z26.array(z26.string().min(1)).optional(),
6384
+ url: z26.string().url().optional(),
6385
+ urlLabel: z26.string().min(1).optional()
6386
+ });
6297
6387
  var ConnectorTypeCompositionShape = {
6298
6388
  /** Which driver executes the fetch. */
6299
6389
  driver: ConnectorDriverSchema,
@@ -6301,6 +6391,8 @@ var ConnectorTypeCompositionShape = {
6301
6391
  configSchema: z26.record(z26.string(), z26.unknown()).optional(),
6302
6392
  /** Shape of the credential the binding stores in the SecretStore. */
6303
6393
  secretShape: z26.record(z26.string(), z26.unknown()).optional(),
6394
+ /** Concise setup guidance rendered above the binding form. */
6395
+ setupInstructions: ConnectorSetupInstructionsSchema.optional(),
6304
6396
  /**
6305
6397
  * Driver-specific fetch config: `{adapterId}` (native) | `{server,list,fetch}`
6306
6398
  * (mcp) | `{fetch|cli}` (script) | `{component,action,...}` (spectral).
@@ -6610,6 +6702,15 @@ var ChatModelMlxSourceSchema = z26.object({
6610
6702
  });
6611
6703
  var ChatModelIdentitySchema = IdentityCommonSchema.extend({
6612
6704
  kind: z26.literal("chat-model"),
6705
+ /**
6706
+ * Organization that created the core model weights when it differs from
6707
+ * the catalog maintainer (for example, a quant maintained by its converter).
6708
+ * Omit when `maintainer` already names the maker.
6709
+ */
6710
+ maker: z26.object({
6711
+ name: z26.string().min(1),
6712
+ url: z26.string().url().optional()
6713
+ }).optional(),
6613
6714
  parameterSize: z26.string(),
6614
6715
  supportsTools: z26.boolean(),
6615
6716
  contextWindow: z26.number().int().positive().optional(),
@@ -6707,6 +6808,11 @@ var ChatModelManifestSchema = z26.object({
6707
6808
  name: z26.string(),
6708
6809
  url: z26.string().url().optional()
6709
6810
  }),
6811
+ /** Core-model maker; present when it differs from `maintainer`. */
6812
+ maker: z26.object({
6813
+ name: z26.string().min(1),
6814
+ url: z26.string().url().optional()
6815
+ }).optional(),
6710
6816
  logo: z26.string().optional(),
6711
6817
  license: z26.string().optional(),
6712
6818
  ...LicenseMetaShape,
@@ -7518,6 +7624,18 @@ var ChatSessionSchema = z28.object({
7518
7624
  }),
7519
7625
  /** Snapshot of the gezel's about.md at session creation, for drift warnings. */
7520
7626
  aboutSnapshot: z28.string().optional(),
7627
+ /**
7628
+ * Per-session override of `config.roleBasedNameOnlyMode` ("boring mode").
7629
+ * Stamped at creation when the creating client has a fixed presentation
7630
+ * mode — the TUI always renders role-based labels, so it pins the
7631
+ * sessions it creates to `true`. This keeps the prompt (what the model
7632
+ * is told about other gezels) consistent with what that client shows;
7633
+ * without it the TUI displayed `reviewer:` while the system prompt said
7634
+ * "the voorman is Tomas", and the model naturally leaked names into
7635
+ * prose. Unset = follow the live config flag, which is what desktop
7636
+ * sessions do.
7637
+ */
7638
+ roleBasedNameOnlyMode: z28.boolean().optional(),
7521
7639
  /** Set when the last attempted resume failed — UI surfaces a banner. */
7522
7640
  resumeFailed: z28.boolean().optional(),
7523
7641
  /**
@@ -7746,7 +7864,14 @@ var CreateChatSessionRequestSchema = z28.object({
7746
7864
  projectId: z28.string().optional(),
7747
7865
  taskRef: z28.string().optional(),
7748
7866
  stepId: z28.string().optional(),
7749
- craftbookRef: z28.string().optional()
7867
+ craftbookRef: z28.string().optional(),
7868
+ /**
7869
+ * Pin the session's name-rendering mode instead of following
7870
+ * `config.roleBasedNameOnlyMode`. Passed by clients whose presentation
7871
+ * mode is fixed (the TUI) so prompt-side name rendering matches their
7872
+ * labels. See `ChatSessionSchema.roleBasedNameOnlyMode`.
7873
+ */
7874
+ roleBasedNameOnlyMode: z28.boolean().optional()
7750
7875
  });
7751
7876
  var ListChatSessionsResponseSchema = z28.object({
7752
7877
  sessions: z28.array(ChatSessionSummarySchema)
@@ -8372,13 +8497,26 @@ var GitHubPullFileSchema = z33.object({
8372
8497
  additions: z33.number().int(),
8373
8498
  deletions: z33.number().int(),
8374
8499
  changes: z33.number().int(),
8375
- /** Truncated unified diff hunk, if returned by GitHub. */
8500
+ /** Unified diff hunk, if requested and returned by GitHub. */
8376
8501
  patch: z33.string().optional(),
8502
+ /** Character count before Gezel's local patch budget was applied. */
8503
+ patchChars: z33.number().int().nonnegative().optional(),
8504
+ /** True when Gezel clipped `patch`; callers must request the file/diff directly. */
8505
+ patchTruncated: z33.boolean().optional(),
8377
8506
  /** Prior path when `status === 'renamed'`. */
8378
8507
  previousFilename: z33.string().optional()
8379
8508
  });
8380
8509
  var ListGitHubPullFilesResponseSchema = z33.object({
8381
- files: z33.array(GitHubPullFileSchema)
8510
+ files: z33.array(GitHubPullFileSchema),
8511
+ /** Total files in the PR before an optional path filter. */
8512
+ allFiles: z33.number().int().nonnegative().optional(),
8513
+ /** Total files selected by the optional path filter. */
8514
+ totalFiles: z33.number().int().nonnegative().optional(),
8515
+ offset: z33.number().int().nonnegative().optional(),
8516
+ limit: z33.number().int().positive().optional(),
8517
+ hasMore: z33.boolean().optional(),
8518
+ nextOffset: z33.number().int().nonnegative().optional(),
8519
+ includesPatch: z33.boolean().optional()
8382
8520
  });
8383
8521
  var GitHubPullCommentSchema = z33.object({
8384
8522
  id: z33.number(),
@@ -8395,7 +8533,14 @@ var ListGitHubPullCommentsResponseSchema = z33.object({
8395
8533
  });
8396
8534
  var GitHubPullDiffResponseSchema = z33.object({
8397
8535
  number: z33.number().int(),
8398
- diff: z33.string()
8536
+ diff: z33.string(),
8537
+ /** Exact changed path when this is a file-scoped diff. */
8538
+ path: z33.string().optional(),
8539
+ offset: z33.number().int().nonnegative().optional(),
8540
+ returnedChars: z33.number().int().nonnegative().optional(),
8541
+ totalChars: z33.number().int().nonnegative().optional(),
8542
+ truncated: z33.boolean().optional(),
8543
+ nextOffset: z33.number().int().nonnegative().optional()
8399
8544
  });
8400
8545
  var GitHubCreateCommentRequestSchema = z33.object({
8401
8546
  body: z33.string().min(1)
@@ -10683,6 +10828,13 @@ var GezelConfigSchema = z37.object({
10683
10828
  * Advanced.
10684
10829
  */
10685
10830
  showAdvancedFeatures: z37.boolean().optional(),
10831
+ /**
10832
+ * When `true`, very early work-in-progress surfaces are revealed in the
10833
+ * UI and CLI. This is a discoverability preference, not a security or
10834
+ * service-layer capability boundary. Defaults on in development builds
10835
+ * and off in releases; an explicit user choice always wins.
10836
+ */
10837
+ showWorkInProgressFeatures: z37.boolean().optional(),
10686
10838
  /**
10687
10839
  * Debug-only opt-in: when `true`, the service rewrites every
10688
10840
  * template-derived gezel's `about.md` back to the prose its catalog
@@ -10959,7 +11111,7 @@ var GezelConfigSchema = z37.object({
10959
11111
  * crew with granular per-step craftbooks. Right for raw-completion
10960
11112
  * local models, where gezel's loop IS the agent.
10961
11113
  * - `flat`: route concrete asks to a single generalist (`start_job` →
10962
- * solo "ambachtsman", which already collapses the craftbook onto the
11114
+ * solo Builder, which already collapses the craftbook onto the
10963
11115
  * one specialist). Right for self-orchestrating providers (codex-cli,
10964
11116
  * anthropic-cli, copilot) that bring their own agent loop — the crew
10965
11117
  * + granular steps are mostly redundant overhead for them (eval data:
@@ -11863,6 +12015,12 @@ var ListProjectsResponseSchema = z37.object({
11863
12015
  var CreateProjectRequestSchema = z37.object({
11864
12016
  name: z37.string().min(1),
11865
12017
  description: z37.string().optional(),
12018
+ /**
12019
+ * Existing local folder to use as the project workspace. Folder-backed
12020
+ * projects are created with ambient Meester progress check-ins disabled;
12021
+ * the user can opt back in from Project Settings.
12022
+ */
12023
+ workingDir: z37.string().min(1).optional(),
11866
12024
  // about/missionObjectives are *encouraged, not required* at the wire level.
11867
12025
  // The New Project dialog still enforces the 60/40 richness minimums for the
11868
12026
  // blank/GitHub flows (where the user is authoring context from scratch), but
@@ -11875,7 +12033,7 @@ var CreateProjectRequestSchema = z37.object({
11875
12033
  missionObjectives: z37.string().optional().describe('Concrete success criteria \u2014 usually a bullet list. What does "done" look like?'),
11876
12034
  /**
11877
12035
  * Project shape. `crew` (default) → traditional voorman-coordinates-
11878
- * specialists. `solo` → a "job": one specialist (the ambachtsman) does
12036
+ * specialists. `solo` → a "job": one Builder does
11879
12037
  * everything themselves; team-management tools are filtered out for
11880
12038
  * sessions on this project.
11881
12039
  */
@@ -15039,6 +15197,7 @@ export {
15039
15197
  CraftbookSchema,
15040
15198
  CraftbookScriptsSchema,
15041
15199
  CraftbookSpawnSchema,
15200
+ CraftbookStepInputSchema,
15042
15201
  CraftbookStepSchema,
15043
15202
  CraftbookSuggestionSchema,
15044
15203
  CraftbookSummarySchema,