@bendyline/gezel 1.0.2 → 1.0.3

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.
package/dist/index.js CHANGED
@@ -500,13 +500,14 @@ var GateCheckSchema = z3.discriminatedUnion("kind", [
500
500
  label: z3.string().min(1).optional(),
501
501
  artifact: z3.boolean().optional()
502
502
  }),
503
- /** `file` must NOT match `pattern` (regex). E.g. exclude internal-only release noise. */
503
+ /** `file` must NOT match `pattern` (regex), in the workspace or artifacts drawer. */
504
504
  z3.object({
505
505
  kind: z3.literal("notContains"),
506
506
  file: z3.string().min(1),
507
507
  pattern: z3.string().min(1),
508
508
  flags: z3.string().optional(),
509
- label: z3.string().min(1).optional()
509
+ label: z3.string().min(1).optional(),
510
+ artifact: z3.boolean().optional()
510
511
  }),
511
512
  /**
512
513
  * `file` may use high-risk claim wording only when the exact matched
@@ -649,6 +650,20 @@ var GateCheckSchema = z3.discriminatedUnion("kind", [
649
650
  tools: z3.array(z3.string().min(1)).min(1),
650
651
  minSuccessful: z3.number().int().positive().optional()
651
652
  }),
653
+ /**
654
+ * A PR-review coverage ledger must name every changed path materialized in
655
+ * a connector corpus. The ledger is workspace JSON (`reviewedFiles` by
656
+ * default); the source records live in the read-only artifacts drawer and
657
+ * carry their authoritative path in frontmatter. This prevents a polished
658
+ * report from passing after only the first context-sized prefix was read.
659
+ */
660
+ z3.object({
661
+ kind: z3.literal("corpusCoverage"),
662
+ file: z3.string().min(1),
663
+ corpusDir: z3.string().min(1),
664
+ reviewedField: z3.string().min(1).optional(),
665
+ recordField: z3.string().min(1).optional()
666
+ }),
652
667
  /**
653
668
  * The H1 slide titles in `file` must match the numbered slide headings in
654
669
  * `outlineFile`, one-for-one and in order. This makes a locked Markdown
@@ -921,6 +936,12 @@ var AdvanceWhenSchema = z5.object({
921
936
  /** Step to activate on the signal. Defaults to `next`; must resolve like `next`. */
922
937
  goto: z5.string().optional()
923
938
  });
939
+ var CraftbookStepInputSchema = z5.object({
940
+ /** Path relative to the selected drawer's root. */
941
+ file: z5.string().min(1).describe("Path relative to the selected drawer root."),
942
+ /** Read from the artifacts drawer instead of the project workspace. */
943
+ artifact: z5.boolean().optional().describe("True for the artifacts drawer; false/omitted for the project workspace.")
944
+ });
924
945
  var ModelTierSchema = z5.enum(MODEL_TIER_ORDER);
925
946
  var CraftbookStepSchema = z5.object({
926
947
  id: z5.string().min(1),
@@ -961,6 +982,10 @@ var CraftbookStepSchema = z5.object({
961
982
  * read the LAST ref's output (legacy routing; prefer gate routing).
962
983
  */
963
984
  onExit: ScriptRefListSchema.optional(),
985
+ /** Required file inputs for this step, in the order they should be opened. */
986
+ consumes: z5.array(CraftbookStepInputSchema).min(1).optional().describe(
987
+ "Files this step must open before working. Artifact inputs also require an explicit `read_artifact` call in the step prompt."
988
+ ),
964
989
  /** See {@link AdvanceWhenSchema}. */
965
990
  advanceWhen: AdvanceWhenSchema.optional(),
966
991
  /** The end-of-step decision. See {@link StepGateSchema} (current) / {@link GateSpecSchema} (legacy). */
@@ -1015,6 +1040,14 @@ function validateCraftbookGraph(cb) {
1015
1040
  problems.push(`step "${s.id}" advanceWhen.goto "${s.advanceWhen.goto}" missing from steps`);
1016
1041
  }
1017
1042
  }
1043
+ for (const input of s.consumes ?? []) {
1044
+ if (!input.artifact) continue;
1045
+ if (!/`read_artifact(?:`|\()/.test(s.prompt ?? "")) {
1046
+ problems.push(
1047
+ `step "${s.id}" consumes artifact "${input.file}" but its prompt does not explicitly call \`read_artifact\``
1048
+ );
1049
+ }
1050
+ }
1018
1051
  if (s.gate) {
1019
1052
  const gate = normalizeStepGate(s.gate);
1020
1053
  if (s.terminal && gate.at === "activation") {
@@ -1307,6 +1340,9 @@ var NewCraftbookStepSchema = z5.object({
1307
1340
  assignee: TaskAssigneeSchema.optional(),
1308
1341
  onEnter: ScriptRefListSchema.optional(),
1309
1342
  onExit: ScriptRefListSchema.optional(),
1343
+ consumes: z5.array(CraftbookStepInputSchema).min(1).optional().describe(
1344
+ "Files this step must open before working. Artifact inputs also require an explicit `read_artifact` call in the step prompt."
1345
+ ),
1310
1346
  advanceWhen: AdvanceWhenSchema.optional(),
1311
1347
  gate: StepGateUnionSchema.optional(),
1312
1348
  /** See {@link StepDeliverableSchema} — one field attaches the enforced gate. */
@@ -1419,6 +1455,7 @@ function resolveSteps(blueprints) {
1419
1455
  ...s.assignee ? { assignee: s.assignee } : {},
1420
1456
  ...s.onEnter ? { onEnter: s.onEnter } : {},
1421
1457
  ...s.onExit ? { onExit: s.onExit } : {},
1458
+ ...s.consumes && s.consumes.length > 0 ? { consumes: s.consumes } : {},
1422
1459
  ...s.advanceWhen ? { advanceWhen: s.advanceWhen } : {},
1423
1460
  ...s.gate ? { gate: s.gate } : {},
1424
1461
  ...s.next ? { next: s.next } : {},
@@ -1541,6 +1578,10 @@ function applyStepPatch(step, patch) {
1541
1578
  delete updated.onExit;
1542
1579
  } else updated.onExit = patch.onExit;
1543
1580
  }
1581
+ if (patch.consumes !== void 0) {
1582
+ if (patch.consumes === null || patch.consumes.length === 0) delete updated.consumes;
1583
+ else updated.consumes = patch.consumes;
1584
+ }
1544
1585
  if (patch.advanceWhen !== void 0) {
1545
1586
  if (patch.advanceWhen === null) delete updated.advanceWhen;
1546
1587
  else updated.advanceWhen = patch.advanceWhen;
@@ -1719,7 +1760,7 @@ function craftbookDocFormatFromEnv(value) {
1719
1760
  }
1720
1761
 
1721
1762
  // src/schemas/craftbook-test.ts
1722
- import { z as z19 } from "zod";
1763
+ import { z as z20 } from "zod";
1723
1764
 
1724
1765
  // src/schemas/history.ts
1725
1766
  import { z as z18 } from "zod";
@@ -3701,7 +3742,15 @@ var ChatEventSchema = z17.discriminatedUnion("type", [
3701
3742
  kind: z17.string(),
3702
3743
  summary: z17.string(),
3703
3744
  at: z17.string(),
3704
- taskRef: z17.string().optional()
3745
+ taskRef: z17.string().optional(),
3746
+ /**
3747
+ * Gezel responsible for the event, when History recorded one. Kept
3748
+ * separate from the human-readable summary so fixed-presentation
3749
+ * clients (notably the role-name-only CLI) can render the actor using
3750
+ * their own naming mode instead of leaking the friendly name embedded
3751
+ * in the audit prose.
3752
+ */
3753
+ gezelId: z17.string().optional()
3705
3754
  }),
3706
3755
  /**
3707
3756
  * Emitted when a gezel crosses a growth level threshold and a pending
@@ -4242,444 +4291,9 @@ var ListHistoryResponseSchema = z18.object({
4242
4291
  entries: z18.array(HistoryEntrySchema)
4243
4292
  });
4244
4293
 
4245
- // src/schemas/craftbook-test.ts
4246
- var CRAFTBOOK_TEST_SCHEMA_VERSION = 1;
4247
- var CRAFTBOOK_TEST_FILENAME = "test.json";
4248
- var PrometheusAlertsCheckSchema = z19.object({
4249
- kind: z19.literal("prometheusAlerts"),
4250
- file: z19.string().min(1),
4251
- minRules: z19.number().int().positive().optional(),
4252
- maxPageAlerts: z19.number().int().nonnegative().optional(),
4253
- allowedSeverities: z19.array(z19.string().min(1)).optional(),
4254
- requiredServices: z19.array(z19.string().min(1)).optional(),
4255
- requiredRunbookUrls: z19.array(z19.string().min(1)).optional()
4256
- }).strict();
4257
- var NodeScriptPassesCheckSchema = z19.object({
4258
- kind: z19.literal("nodeScriptPasses"),
4259
- script: z19.string().min(1),
4260
- timeoutMs: z19.number().int().positive().optional(),
4261
- requiredOutput: z19.array(
4262
- z19.object({
4263
- pattern: z19.string().min(1),
4264
- flags: z19.string().optional(),
4265
- label: z19.string().optional()
4266
- }).strict()
4267
- ).optional()
4268
- }).strict();
4269
- var BinaryDocumentCheckSchema = z19.object({
4270
- kind: z19.literal("binaryDocument"),
4271
- file: z19.string().min(1),
4272
- /** Look in the artifacts drawer instead of the workspace. */
4273
- artifact: z19.boolean().optional(),
4274
- /** Floor on the container's byte length; defaults to 1000. */
4275
- minBytes: z19.number().int().positive().optional()
4276
- }).strict();
4277
- var CraftbookTestCheckSchema = z19.union([
4278
- GateCheckSchema,
4279
- PrometheusAlertsCheckSchema,
4280
- NodeScriptPassesCheckSchema,
4281
- BinaryDocumentCheckSchema
4282
- ]);
4283
- var MockServiceIdSchema = z19.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "mock service ids are lowercase kebab-case");
4284
- var MockToolsetIdSchema = z19.string().min(1).regex(
4285
- /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,
4286
- "mock toolset ids are lowercase catalog ids or scoped npm-style ids"
4287
- );
4288
- var MockHttpRouteSchema = z19.object({
4289
- method: z19.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
4290
- /** Exact path, `:param` segments, or a trailing `*` wildcard. */
4291
- path: z19.string().min(1),
4292
- status: z19.number().int().min(100).max(599).default(200),
4293
- headers: z19.record(z19.string(), z19.string()).optional(),
4294
- /** String bodies are served verbatim; anything else is JSON-encoded. */
4295
- body: z19.unknown(),
4296
- latencyMs: z19.number().int().nonnegative().optional()
4297
- }).strict();
4298
- var MockServiceSchema = z19.discriminatedUnion("kind", [
4299
- z19.object({
4300
- kind: z19.literal("http"),
4301
- id: MockServiceIdSchema,
4302
- description: z19.string().min(1),
4303
- /**
4304
- * v1 mock HTTP is reachable ONLY through the `http.authed`
4305
- * credential rail (anonymous script/browser HTTP hard-rejects
4306
- * loopback), so a credential is required. The harness seeds it and
4307
- * grants the mock's exact origin on the trial project.
4308
- */
4309
- credential: z19.object({
4310
- name: z19.string().regex(/^mock\.[a-z0-9][a-z0-9.-]*$/, "mock credentials are named mock.<service-id>"),
4311
- authScheme: z19.enum(["bearer", "basic"]).optional()
4312
- }).strict(),
4313
- routes: z19.array(MockHttpRouteSchema).min(1)
4314
- }).strict(),
4315
- z19.object({
4316
- kind: z19.literal("webhook"),
4317
- id: MockServiceIdSchema,
4318
- description: z19.string().min(1),
4319
- /** Receiver path; defaults to `/webhook` when omitted. */
4320
- path: z19.string().min(1).optional()
4321
- }).strict(),
4322
- z19.object({
4323
- kind: z19.literal("cli"),
4324
- id: MockServiceIdSchema,
4325
- description: z19.string().min(1),
4326
- /** Fake-CLI shim seeded as a workspace file (today's dry-run pattern). */
4327
- shim: z19.object({ path: z19.string().min(1), content: z19.string() }).strict()
4328
- }).strict(),
4329
- z19.object({
4330
- kind: z19.literal("mcp"),
4331
- id: MockServiceIdSchema,
4332
- description: z19.string().min(1),
4333
- /** Override the local-catalog id when the mock replaces a real dependency. */
4334
- toolsetId: MockToolsetIdSchema.optional(),
4335
- /**
4336
- * Served live by the eval mock rail: each declared tool becomes a
4337
- * real tool on a per-trial Streamable-HTTP MCP endpoint, installed
4338
- * into the trial via a local-catalog `mock-mcp-<id>` toolset.
4339
- * `resultTemplate` is JSON-encoded as the tool result text
4340
- * (default `{"ok":true}` when absent).
4341
- */
4342
- tools: z19.array(
4343
- z19.object({
4344
- name: z19.string().min(1),
4345
- description: z19.string().min(1),
4346
- resultTemplate: z19.unknown().optional(),
4347
- /** Deterministic stateful responses, consumed in call order; the last repeats. */
4348
- resultSequence: z19.array(z19.unknown()).min(1).optional(),
4349
- /**
4350
- * Deterministic eval-only file materialization after this
4351
- * tool call. The fixture must match the container the
4352
- * deliverable's extension claims — a `minimal-pptx` written
4353
- * to a `.docx` path now fails the `binaryDocument` check on
4354
- * content type rather than sliding through a byte floor.
4355
- */
4356
- writeFixture: z19.object({
4357
- surface: z19.enum(["workspace", "artifact"]),
4358
- pathArgument: z19.string().min(1),
4359
- fixture: z19.enum(["minimal-pptx", "minimal-docx", "minimal-pdf", "minimal-png"])
4360
- }).strict().optional()
4361
- }).strict()
4362
- ).min(1)
4363
- }).strict()
4364
- ]);
4365
- var CraftbookTestFixtureFileSchema = z19.object({
4366
- path: z19.string().min(1),
4367
- content: z19.string(),
4368
- /** Defaults to `workspace`; `harness` never enters the model-visible project. */
4369
- surface: z19.enum(["workspace", "artifact", "harness"]).optional(),
4370
- /**
4371
- * Whether the fixture is presented to the model as source material.
4372
- * Defaults to true; false still seeds the file for browsers and graders.
4373
- */
4374
- modelInput: z19.boolean().optional()
4375
- }).strict();
4376
- var CraftbookTestWorkerSchema = z19.object({
4377
- name: z19.string().min(1),
4378
- role: z19.string().min(1),
4379
- description: z19.string().optional(),
4380
- about: z19.string().optional()
4381
- }).strict();
4382
- var CraftbookTestSetupSchema = z19.object({
4383
- projectName: z19.string().min(1),
4384
- about: z19.string().optional(),
4385
- missionObjectives: z19.string().optional(),
4386
- files: z19.array(CraftbookTestFixtureFileSchema).default([]),
4387
- /** Exact values supplied to the catalog craftbook's `paramSchema`. */
4388
- craftbookParams: z19.record(z19.string(), z19.string()).optional(),
4389
- /**
4390
- * Direct execution target. When present the harness seeds this gezel
4391
- * and sends the kickoff straight to it (measuring whether the book
4392
- * guides the work); absent → the Meester routes.
4393
- */
4394
- worker: CraftbookTestWorkerSchema.optional()
4395
- }).strict();
4396
- var CraftbookTestDeliverableSchema = z19.object({
4397
- path: z19.string().min(1),
4398
- kind: DeliverableKindSchema,
4399
- minBytes: z19.number().int().positive().optional(),
4400
- checks: z19.array(CraftbookTestCheckSchema).optional()
4401
- }).strict();
4402
- var CraftbookTestMockExpectationSchema = z19.object({
4403
- /** Mock service id from `mocks[]`. */
4404
- service: MockServiceIdSchema,
4405
- minRequests: z19.number().int().positive().optional(),
4406
- /** Regex sources matched against logged request paths. */
4407
- requiredPaths: z19.array(z19.string().min(1)).optional(),
4408
- forbiddenPaths: z19.array(z19.string().min(1)).optional(),
4409
- /**
4410
- * Exact MCP tool names that must each have been called at least once
4411
- * on this service (`kind: 'mcp'` only). Exact names, not regexes —
4412
- * the tool roster is fully declared in the same file, so a pattern
4413
- * buys nothing and invites drift. Cross-checked against the mock's
4414
- * declared `tools[]` at parse time.
4415
- */
4416
- requiredTools: z19.array(z19.string().min(1)).optional(),
4417
- /** Per-MCP-tool call budgets for repeated journeys or retries. */
4418
- toolCalls: z19.record(
4419
- z19.string().min(1),
4420
- z19.object({
4421
- minCalls: z19.number().int().nonnegative().default(1),
4422
- maxCalls: z19.number().int().nonnegative().optional()
4423
- }).strict().refine(
4424
- (value) => value.maxCalls === void 0 || value.minCalls <= value.maxCalls,
4425
- "minCalls must be less than or equal to maxCalls"
4426
- )
4427
- ).optional()
4428
- }).strict();
4429
- var CraftbookTestHistoryExpectationSchema = z19.object({
4430
- kind: HistoryEventKindSchema,
4431
- minEntries: z19.number().int().nonnegative().default(1),
4432
- maxEntries: z19.number().int().nonnegative().optional(),
4433
- summaryPattern: z19.string().min(1).optional(),
4434
- flags: z19.string().optional(),
4435
- details: z19.record(z19.string(), z19.union([z19.string(), z19.number(), z19.boolean(), z19.null()])).optional()
4436
- }).strict();
4437
- var CraftbookTestSuccessSchema = z19.object({
4438
- summary: z19.string().min(1),
4439
- deliverables: z19.array(CraftbookTestDeliverableSchema).optional(),
4440
- checks: z19.array(CraftbookTestCheckSchema).optional(),
4441
- taskNotes: z19.object({
4442
- minBytes: z19.number().int().positive().optional(),
4443
- checks: z19.array(CraftbookTestCheckSchema).optional(),
4444
- requireCraftbookTask: z19.boolean().optional()
4445
- }).strict().optional(),
4446
- taskGraph: z19.object({
4447
- checks: z19.array(CraftbookTestCheckSchema).optional(),
4448
- requireCraftbookTask: z19.boolean().optional(),
4449
- /** Require the matching task to reach a terminal step (or complete). */
4450
- requireTerminalStep: z19.boolean().optional(),
4451
- requireDraftRef: z19.boolean().optional(),
4452
- draft: z19.object({
4453
- status: z19.enum(["draft", "paused", "active", "complete", "canceled"]).optional(),
4454
- minDescriptionBytes: z19.number().int().positive().optional(),
4455
- minOutcomes: z19.number().int().positive().optional(),
4456
- minSteps: z19.number().int().positive().optional(),
4457
- requireTerminalVerification: z19.boolean().optional(),
4458
- requireGatedBuildSteps: z19.boolean().optional()
4459
- }).strict().optional()
4460
- }).strict().optional(),
4461
- /** Assertions evaluated against the live mock server's request log. */
4462
- mocks: z19.array(CraftbookTestMockExpectationSchema).optional(),
4463
- /** Assertions evaluated against the project's append-only History log. */
4464
- history: z19.array(CraftbookTestHistoryExpectationSchema).optional(),
4465
- /** Workspace fixtures whose final content must equal the seeded bytes exactly. */
4466
- unchangedFixtures: z19.array(z19.string().min(1)).optional()
4467
- }).strict();
4468
- var CraftbookTestRubricSchema = z19.object({
4469
- artifact: z19.object({
4470
- /** Workspace or artifact path the judge reads (adapter derives the basename). */
4471
- path: z19.string().min(1),
4472
- kind: z19.enum(["html", "markdown", "yaml", "typescript", "json", "text"])
4473
- }).strict(),
4474
- axes: z19.array(z19.object({ name: z19.string().min(1), description: z19.string().min(1) }).strict()).min(1),
4475
- contextNote: z19.string().optional()
4476
- }).strict();
4477
- var CraftbookTestSpecSchema = z19.object({
4478
- schemaVersion: z19.literal(CRAFTBOOK_TEST_SCHEMA_VERSION),
4479
- title: z19.string().min(1),
4480
- objective: z19.string().min(1),
4481
- /**
4482
- * Task-class taxonomy tags (e.g. `html-game`, `corpus`, `external`).
4483
- * The single declared source for harness selection and batch
4484
- * planning — replaces the old regex classifiers.
4485
- */
4486
- tags: z19.array(z19.string().min(1)).default([]),
4487
- /** Kickoff chat message the harness sends. Required — every book runs. */
4488
- prompt: z19.string().min(1),
4489
- setup: CraftbookTestSetupSchema,
4490
- mocks: z19.array(MockServiceSchema).default([]),
4491
- success: CraftbookTestSuccessSchema,
4492
- rubric: CraftbookTestRubricSchema,
4493
- qualityFocus: z19.array(z19.string().min(1)).default([]),
4494
- /**
4495
- * Sanctioned escape hatch for experiments — carried opaquely, never
4496
- * interpreted by CI. Promote a field out of here before relying on it.
4497
- */
4498
- extensions: z19.record(z19.string(), z19.unknown()).optional()
4499
- }).strict().superRefine((spec, ctx) => {
4500
- const mockIds = new Set(spec.mocks.map((m) => m.id));
4501
- const mockById = new Map(spec.mocks.map((m) => [m.id, m]));
4502
- for (const [i, expectation] of (spec.success.mocks ?? []).entries()) {
4503
- if (!mockIds.has(expectation.service)) {
4504
- ctx.addIssue({
4505
- code: z19.ZodIssueCode.custom,
4506
- path: ["success", "mocks", i, "service"],
4507
- message: `success.mocks[${i}] references unknown mock service "${expectation.service}"`
4508
- });
4509
- }
4510
- if (expectation.requiredTools && expectation.requiredTools.length > 0) {
4511
- const target = mockById.get(expectation.service);
4512
- if (target && target.kind !== "mcp") {
4513
- ctx.addIssue({
4514
- code: z19.ZodIssueCode.custom,
4515
- path: ["success", "mocks", i, "requiredTools"],
4516
- message: `success.mocks[${i}].requiredTools requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4517
- });
4518
- } else if (target?.kind === "mcp") {
4519
- const declared = new Set(target.tools.map((tool) => tool.name));
4520
- for (const name of expectation.requiredTools) {
4521
- if (!declared.has(name)) {
4522
- ctx.addIssue({
4523
- code: z19.ZodIssueCode.custom,
4524
- path: ["success", "mocks", i, "requiredTools"],
4525
- message: `success.mocks[${i}].requiredTools names undeclared tool "${name}" on mcp service "${expectation.service}"`
4526
- });
4527
- }
4528
- }
4529
- }
4530
- }
4531
- if (expectation.toolCalls && Object.keys(expectation.toolCalls).length > 0) {
4532
- const target = mockById.get(expectation.service);
4533
- if (target && target.kind !== "mcp") {
4534
- ctx.addIssue({
4535
- code: z19.ZodIssueCode.custom,
4536
- path: ["success", "mocks", i, "toolCalls"],
4537
- message: `success.mocks[${i}].toolCalls requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4538
- });
4539
- } else if (target?.kind === "mcp") {
4540
- const declared = new Set(target.tools.map((tool) => tool.name));
4541
- for (const name of Object.keys(expectation.toolCalls)) {
4542
- if (!declared.has(name)) {
4543
- ctx.addIssue({
4544
- code: z19.ZodIssueCode.custom,
4545
- path: ["success", "mocks", i, "toolCalls", name],
4546
- message: `success.mocks[${i}].toolCalls names undeclared tool "${name}" on mcp service "${expectation.service}"`
4547
- });
4548
- }
4549
- }
4550
- }
4551
- }
4552
- }
4553
- for (const [i, mock] of spec.mocks.entries()) {
4554
- if (mock.kind === "http" && mock.credential.name !== `mock.${mock.id}`) {
4555
- ctx.addIssue({
4556
- code: z19.ZodIssueCode.custom,
4557
- path: ["mocks", i, "credential", "name"],
4558
- message: `http mock "${mock.id}" must use credential name "mock.${mock.id}"`
4559
- });
4560
- }
4561
- }
4562
- const workspaceFixtures = new Set(
4563
- spec.setup.files.filter((file) => file.surface === void 0 || file.surface === "workspace").map((file) => file.path)
4564
- );
4565
- for (const [i, path] of (spec.success.unchangedFixtures ?? []).entries()) {
4566
- if (!workspaceFixtures.has(path)) {
4567
- ctx.addIssue({
4568
- code: z19.ZodIssueCode.custom,
4569
- path: ["success", "unchangedFixtures", i],
4570
- message: `unchanged fixture "${path}" is not a seeded workspace file`
4571
- });
4572
- }
4573
- }
4574
- for (const [i, expectation] of (spec.success.history ?? []).entries()) {
4575
- if (expectation.maxEntries !== void 0 && expectation.minEntries > expectation.maxEntries) {
4576
- ctx.addIssue({
4577
- code: z19.ZodIssueCode.custom,
4578
- path: ["success", "history", i],
4579
- message: "minEntries must be less than or equal to maxEntries"
4580
- });
4581
- }
4582
- }
4583
- });
4584
- function parseCraftbookTestSpec(raw, opts) {
4585
- const mode = opts?.mode ?? "strict";
4586
- const candidate = mode === "tolerant" ? deepStripUnknown(raw) : raw;
4587
- const parsed = CraftbookTestSpecSchema.safeParse(candidate);
4588
- if (parsed.success) return { ok: true, spec: parsed.data };
4589
- return {
4590
- ok: false,
4591
- errors: parsed.error.issues.map((issue) => {
4592
- const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
4593
- return `${path}: ${issue.message}`;
4594
- })
4595
- };
4596
- }
4597
- function deepStripUnknown(raw) {
4598
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
4599
- const record = structuredClone(raw);
4600
- const version = record.schemaVersion;
4601
- if (typeof version === "number" && Number.isInteger(version) && version > CRAFTBOOK_TEST_SCHEMA_VERSION) {
4602
- record.schemaVersion = CRAFTBOOK_TEST_SCHEMA_VERSION;
4603
- }
4604
- for (let pass = 0; pass < 16; pass++) {
4605
- const attempt = CraftbookTestSpecSchema.safeParse(record);
4606
- if (attempt.success) return record;
4607
- const unknownKeyIssues = attempt.error.issues.filter(
4608
- (issue) => issue.code === z19.ZodIssueCode.unrecognized_keys
4609
- );
4610
- if (unknownKeyIssues.length === 0) return record;
4611
- for (const issue of unknownKeyIssues) {
4612
- const target = resolvePath(record, issue.path);
4613
- if (target && typeof target === "object" && !Array.isArray(target)) {
4614
- for (const key of issue.keys) delete target[key];
4615
- }
4616
- }
4617
- }
4618
- return record;
4619
- }
4620
- function resolvePath(root, path) {
4621
- let node = root;
4622
- for (const segment of path) {
4623
- if (node === null || typeof node !== "object") return void 0;
4624
- node = node[segment];
4625
- }
4626
- return node;
4627
- }
4628
-
4629
- // src/schemas/codex-setup.ts
4630
- import { z as z20 } from "zod";
4631
- var CodexSetupModelOptionSchema = z20.object({
4632
- id: z20.string().min(1),
4633
- label: z20.string().min(1),
4634
- description: z20.string().optional(),
4635
- kind: z20.enum(["gezel", "model"]).default("model"),
4636
- provider: z20.string().min(1),
4637
- /** Stable gezel id for persona-backed entries. Absent on raw-model entries. */
4638
- gezelId: z20.string().min(1).optional(),
4639
- role: z20.string().min(1).optional(),
4640
- /** Human-readable name of the effective inference model behind a gezel. */
4641
- modelLabel: z20.string().min(1).optional(),
4642
- contextWindow: z20.number().int().positive().optional(),
4643
- supportsReasoning: z20.boolean().optional(),
4644
- supportsTools: z20.boolean().optional()
4645
- });
4646
- var CodexSetupStateSchema = z20.enum([
4647
- "not-configured",
4648
- "configured",
4649
- "update-needed",
4650
- "conflict",
4651
- "unavailable"
4652
- ]);
4653
- var CodexSetupStatusResponseSchema = z20.object({
4654
- state: CodexSetupStateSchema,
4655
- models: z20.array(CodexSetupModelOptionSchema),
4656
- configuredModel: z20.string().optional(),
4657
- recommendedModel: z20.string().optional(),
4658
- reasons: z20.array(z20.string()),
4659
- message: z20.string().optional(),
4660
- codexInstalled: z20.boolean(),
4661
- codexVersion: z20.string().optional(),
4662
- codexPath: z20.string().optional(),
4663
- endpointsEnabled: z20.boolean(),
4664
- profileName: z20.string().min(1),
4665
- profilePath: z20.string().min(1),
4666
- launchCommand: z20.string().min(1),
4667
- bridge: z20.object({
4668
- baseUrl: z20.string().url(),
4669
- listening: z20.boolean(),
4670
- port: z20.number().int().nonnegative()
4671
- }),
4672
- canConfigure: z20.boolean(),
4673
- /** Whether Gezel-owned credential/state material exists and can be safely removed. */
4674
- canRemove: z20.boolean()
4675
- });
4676
- var ConfigureCodexRequestSchema = z20.object({
4677
- model: z20.string().min(1)
4678
- });
4679
-
4680
4294
  // src/schemas/project.ts
4681
- import { z as z21 } from "zod";
4682
- var HttpsOriginSchema = z21.string().url().refine(
4295
+ import { z as z19 } from "zod";
4296
+ var HttpsOriginSchema = z19.string().url().refine(
4683
4297
  (value) => {
4684
4298
  try {
4685
4299
  const url = new URL(value);
@@ -4690,7 +4304,7 @@ var HttpsOriginSchema = z21.string().url().refine(
4690
4304
  },
4691
4305
  { message: "must be an exact HTTPS origin (for example https://api.example.com)" }
4692
4306
  );
4693
- var ProjectGitHubSchema = z21.object({
4307
+ var ProjectGitHubSchema = z19.object({
4694
4308
  // Accept any non-empty string. Git URLs come in many forms beyond
4695
4309
  // `https://`: ssh shorthand (`git@github.com:owner/repo`), local
4696
4310
  // filesystem paths (`/path/to/bare.git`), other-host references.
@@ -4698,89 +4312,89 @@ var ProjectGitHubSchema = z21.object({
4698
4312
  // and broke Phase-3 worktree tests that use a local bare repo as
4699
4313
  // the upstream. Higher layers (the github sync code) parse the URL
4700
4314
  // and reject anything that doesn't shape up as a clonable ref.
4701
- url: z21.string().min(1),
4702
- branch: z21.string().optional(),
4315
+ url: z19.string().min(1),
4316
+ branch: z19.string().optional(),
4703
4317
  /** Resolved absolute path to the working tree. Managed by the service. */
4704
- checkoutDir: z21.string().optional(),
4705
- lastSyncedAt: z21.string().optional(),
4318
+ checkoutDir: z19.string().optional(),
4319
+ lastSyncedAt: z19.string().optional(),
4706
4320
  /** Repo default branch (e.g. "main"), detected lazily and cached. Managed by the service. */
4707
- defaultBranch: z21.string().optional()
4321
+ defaultBranch: z19.string().optional()
4708
4322
  });
4709
- var ProjectConnectorBindingSchema = z21.object({
4323
+ var ProjectConnectorBindingSchema = z19.object({
4710
4324
  /** Stable binding id; also the SecretStore `fieldId` + corpus-slug seed. */
4711
- id: z21.string().min(1),
4325
+ id: z19.string().min(1),
4712
4326
  /** The connector-type catalog id, e.g. `mail-gmail`, `linear-issues`. */
4713
- type: z21.string().min(1),
4327
+ type: z19.string().min(1),
4714
4328
  /** Catalog source the type resolved from (provenance/pin). */
4715
- sourceId: z21.string().optional(),
4329
+ sourceId: z19.string().optional(),
4716
4330
  /** Pinned connector-type version. */
4717
- version: z21.string().optional(),
4718
- displayName: z21.string().optional(),
4331
+ version: z19.string().optional(),
4332
+ displayName: z19.string().optional(),
4719
4333
  /**
4720
4334
  * Artifact-relative corpus root (`data/<corpusName>`), resolved once at bind
4721
4335
  * time and never recomputed — renaming a binding must not strand its corpus.
4722
4336
  */
4723
- corpusDir: z21.string().optional(),
4337
+ corpusDir: z19.string().optional(),
4724
4338
  /** Per-binding config, validated at bind time against the type's `configSchema`. */
4725
- config: z21.record(z21.string(), z21.unknown()).default({}),
4339
+ config: z19.record(z19.string(), z19.unknown()).default({}),
4726
4340
  /** Opaque, adapter-shaped incremental-sync cursor. Persisted so resync resumes. */
4727
- cursor: z21.unknown().optional(),
4341
+ cursor: z19.unknown().optional(),
4728
4342
  /** Pause syncing without unbinding. */
4729
- disabled: z21.boolean().optional(),
4730
- lastSyncedAt: z21.string().optional(),
4343
+ disabled: z19.boolean().optional(),
4344
+ lastSyncedAt: z19.string().optional(),
4731
4345
  /** Last sync error, surfaced in the UI; cleared on the next success. */
4732
- lastError: z21.string().optional()
4346
+ lastError: z19.string().optional()
4733
4347
  });
4734
- var ProjectNudgeConfigSchema = z21.object({
4735
- enabled: z21.boolean().optional(),
4736
- rapidIntervalMs: z21.number().int().positive().optional(),
4737
- slowIntervalMs: z21.number().int().positive().optional(),
4738
- recentActivityWindowMs: z21.number().int().positive().optional(),
4739
- rapidAttemptsBeforeBackoff: z21.number().int().positive().optional(),
4348
+ var ProjectNudgeConfigSchema = z19.object({
4349
+ enabled: z19.boolean().optional(),
4350
+ rapidIntervalMs: z19.number().int().positive().optional(),
4351
+ slowIntervalMs: z19.number().int().positive().optional(),
4352
+ recentActivityWindowMs: z19.number().int().positive().optional(),
4353
+ rapidAttemptsBeforeBackoff: z19.number().int().positive().optional(),
4740
4354
  /**
4741
4355
  * Grace period applied to the very first nudge a project ever
4742
4356
  * receives, measured from `project.createdAt`. Default per tempo;
4743
4357
  * setting `0` opts out (legacy behavior — first nudge fires as
4744
4358
  * soon as the rapid interval allows).
4745
4359
  */
4746
- firstNudgeGraceMs: z21.number().int().nonnegative().optional()
4360
+ firstNudgeGraceMs: z19.number().int().nonnegative().optional()
4747
4361
  });
4748
- var ProjectNudgeStateSchema = z21.object({
4749
- lastNudgedAt: z21.string().optional(),
4750
- consecutiveRapidNudges: z21.number().int().nonnegative().optional()
4362
+ var ProjectNudgeStateSchema = z19.object({
4363
+ lastNudgedAt: z19.string().optional(),
4364
+ consecutiveRapidNudges: z19.number().int().nonnegative().optional()
4751
4365
  });
4752
- var ProjectTabVisibilitySchema = z21.object({
4753
- overview: z21.boolean().optional(),
4754
- tasks: z21.boolean().optional(),
4755
- approvals: z21.boolean().optional(),
4756
- workspace: z21.boolean().optional(),
4757
- artifacts: z21.boolean().optional(),
4758
- map: z21.boolean().optional()
4366
+ var ProjectTabVisibilitySchema = z19.object({
4367
+ overview: z19.boolean().optional(),
4368
+ tasks: z19.boolean().optional(),
4369
+ approvals: z19.boolean().optional(),
4370
+ workspace: z19.boolean().optional(),
4371
+ artifacts: z19.boolean().optional(),
4372
+ map: z19.boolean().optional()
4759
4373
  }).strict();
4760
- var ProjectManagedWorkspaceWritePolicySchema = z21.enum(["auto", "allow", "deny"]);
4761
- var ProjectTypeProvenanceSchema = z21.object({
4374
+ var ProjectManagedWorkspaceWritePolicySchema = z19.enum(["auto", "allow", "deny"]);
4375
+ var ProjectTypeProvenanceSchema = z19.object({
4762
4376
  /** Catalog id of the applied project type. */
4763
- id: z21.string(),
4377
+ id: z19.string(),
4764
4378
  /** Type version installed at adoption. */
4765
- version: z21.string(),
4379
+ version: z19.string(),
4766
4380
  /** Catalog source the type resolved from (`bundled` | `local` | `community` | …). */
4767
- source: z21.string(),
4381
+ source: z19.string(),
4768
4382
  /** Param values collected at adoption, substituted into templates + seed files. */
4769
- params: z21.record(z21.string(), z21.unknown()).optional(),
4383
+ params: z19.record(z19.string(), z19.unknown()).optional(),
4770
4384
  /** ISO timestamp of adoption. */
4771
- appliedAt: z21.string()
4385
+ appliedAt: z19.string()
4772
4386
  });
4773
- var ProjectSchema = z21.object({
4387
+ var ProjectSchema = z19.object({
4774
4388
  id: EntityIdSchema,
4775
- name: z21.string(),
4776
- description: z21.string().optional(),
4777
- workingDir: z21.string().optional(),
4389
+ name: z19.string(),
4390
+ description: z19.string().optional(),
4391
+ workingDir: z19.string().optional(),
4778
4392
  /** Optional gezel that acts as the project's voorman (foreman). Surfaces in
4779
4393
  * the project detail pane, flows into the system prompt when a session is
4780
4394
  * scoped here. For solo projects (`mode === 'solo'`) this same field
4781
4395
  * holds the project's ambachtsman — the data is unchanged, only the
4782
4396
  * label flips. */
4783
- voormanGezelId: z21.string().optional(),
4397
+ voormanGezelId: z19.string().optional(),
4784
4398
  /**
4785
4399
  * Internal marker: ISO timestamp of the one-time automatic voorman
4786
4400
  * assignment. The indexer ensures every project ends up with a voorman
@@ -4790,7 +4404,7 @@ var ProjectSchema = z21.object({
4790
4404
  * silently re-populated on the next scan. Not user-editable; absent on
4791
4405
  * projects last written before this field existed.
4792
4406
  */
4793
- voormanAutoAssignedAt: z21.string().optional(),
4407
+ voormanAutoAssignedAt: z19.string().optional(),
4794
4408
  /**
4795
4409
  * Roster — gezels that have been pulled into this project. Populated
4796
4410
  * automatically the first time a gezel is set as voorman, opens a
@@ -4805,7 +4419,7 @@ var ProjectSchema = z21.object({
4805
4419
  * (back-compat with every project written before this field
4806
4420
  * existed).
4807
4421
  */
4808
- gezelIds: z21.array(z21.string()).optional(),
4422
+ gezelIds: z19.array(z19.string()).optional(),
4809
4423
  /**
4810
4424
  * Suggested-work keys the user has dismissed ("don't offer this again
4811
4425
  * here"). Advisory UI state, same spirit as `gezelIds`: enabling a
@@ -4815,7 +4429,7 @@ var ProjectSchema = z21.object({
4815
4429
  * `project-type:<typeId>:<scheduleKey>`). Deliberately not exposed
4816
4430
  * through the model-facing `update_project` MCP tool.
4817
4431
  */
4818
- suggestedWorkDismissed: z21.array(z21.string()).optional(),
4432
+ suggestedWorkDismissed: z19.array(z19.string()).optional(),
4819
4433
  /**
4820
4434
  * Shared per-project configuration values ("project properties") that
4821
4435
  * craftbook params and features draw from — e.g. `content.language`,
@@ -4824,7 +4438,7 @@ var ProjectSchema = z21.object({
4824
4438
  * ids are allowed (the registry improves display, it doesn't gate).
4825
4439
  * Values are plain strings; empty string is treated as unset.
4826
4440
  */
4827
- properties: z21.record(z21.string(), z21.string()).optional(),
4441
+ properties: z19.record(z19.string(), z19.string()).optional(),
4828
4442
  /**
4829
4443
  * Project shape. `crew` (the default) is the original behavior — the
4830
4444
  * voorman recruits and coordinates a team of specialists. `solo` is a
@@ -4835,7 +4449,7 @@ var ProjectSchema = z21.object({
4835
4449
  * for back-compat with every project on disk before this field
4836
4450
  * existed.
4837
4451
  */
4838
- mode: z21.enum(["crew", "solo"]).optional(),
4452
+ mode: z19.enum(["crew", "solo"]).optional(),
4839
4453
  /**
4840
4454
  * Optional custom label for this project's lead gezel, overriding the
4841
4455
  * mode-based default ("Voorman" / "Ambachtsman") everywhere the UI
@@ -4843,7 +4457,7 @@ var ProjectSchema = z21.object({
4843
4457
  * "Opponent"); absent → the mode default. The data field stays
4844
4458
  * `voormanGezelId` — only the label changes.
4845
4459
  */
4846
- leadLabel: z21.string().optional(),
4460
+ leadLabel: z19.string().optional(),
4847
4461
  /**
4848
4462
  * Lean-agent profile (set by a project type at adoption, e.g. checkers).
4849
4463
  * When true, sessions here get a minimal tool surface (the type's script
@@ -4851,7 +4465,7 @@ var ProjectSchema = z21.object({
4851
4465
  * scaffolding). Keeps small local models from being overwhelmed on a
4852
4466
  * focused single-purpose task. Absent → the full agent profile.
4853
4467
  */
4854
- leanProfile: z21.boolean().optional(),
4468
+ leanProfile: z19.boolean().optional(),
4855
4469
  /**
4856
4470
  * Per-project workspace-indexing switch. Missing/true preserves the
4857
4471
  * historical behavior: structural discovery plus the content-index refresh
@@ -4864,9 +4478,9 @@ var ProjectSchema = z21.object({
4864
4478
  * document index. Project-type manifests may seed the value at adoption and
4865
4479
  * the user can override it later in Project Settings.
4866
4480
  */
4867
- indexingEnabled: z21.boolean().optional(),
4481
+ indexingEnabled: z19.boolean().optional(),
4868
4482
  github: ProjectGitHubSchema.optional(),
4869
- connectors: z21.array(ProjectConnectorBindingSchema).optional(),
4483
+ connectors: z19.array(ProjectConnectorBindingSchema).optional(),
4870
4484
  nudgeConfig: ProjectNudgeConfigSchema.optional(),
4871
4485
  nudgeState: ProjectNudgeStateSchema.optional(),
4872
4486
  /**
@@ -4889,7 +4503,7 @@ var ProjectSchema = z21.object({
4889
4503
  * @deprecated Use `managedWorkspaceWritePolicy` and the centralized
4890
4504
  * `projectManagedWorkspaceWritable` resolver.
4891
4505
  */
4892
- allowGezelWrites: z21.boolean().optional(),
4506
+ allowGezelWrites: z19.boolean().optional(),
4893
4507
  /**
4894
4508
  * Per-project Codex execution posture selected from the project status bar.
4895
4509
  * It overrides per-gezel/install Codex defaults so the visible control is
@@ -4926,7 +4540,7 @@ var ProjectSchema = z21.object({
4926
4540
  * weak local model having to take an explicit action. Reversible and
4927
4541
  * non-destructive; chat and direct tool calls keep working.
4928
4542
  */
4929
- status: z21.enum(["active", "readonly", "inactive", "stable"]).optional(),
4543
+ status: z19.enum(["active", "readonly", "inactive", "stable"]).optional(),
4930
4544
  /**
4931
4545
  * Bury this project in the navigation without deleting it. Archived
4932
4546
  * projects remain available from the dedicated section in the full
@@ -4936,13 +4550,13 @@ var ProjectSchema = z21.object({
4936
4550
  * Missing/false means visible in the ordinary project UX, preserving
4937
4551
  * compatibility with projects written before archiving existed.
4938
4552
  */
4939
- archived: z21.boolean().optional(),
4553
+ archived: z19.boolean().optional(),
4940
4554
  /**
4941
4555
  * Per-project override of the `run_nodejs_script` wall-clock
4942
4556
  * timeout. Clamped between 30 seconds and 30 minutes. Missing →
4943
4557
  * the service-side default (5 min) applies.
4944
4558
  */
4945
- workspaceScriptTimeoutMs: z21.number().int().min(3e4).max(30 * 6e4).optional(),
4559
+ workspaceScriptTimeoutMs: z19.number().int().min(3e4).max(30 * 6e4).optional(),
4946
4560
  /**
4947
4561
  * Named credentials this project is explicitly allowed to use.
4948
4562
  * Credentials are stored once globally in `SecretStore`; a grant
@@ -4951,76 +4565,515 @@ var ProjectSchema = z21.object({
4951
4565
  * Missing → no credentials granted. See `scripts/dispatcher.ts`
4952
4566
  * and `secrets/registry.ts` for resolution.
4953
4567
  */
4954
- grantedCredentials: z21.array(z21.string()).optional(),
4568
+ grantedCredentials: z19.array(z19.string()).optional(),
4569
+ /**
4570
+ * Advanced exact-origin bindings for toolset credentials. Built-in provider
4571
+ * credentials are service-pinned and webhook credentials follow the
4572
+ * configured webhook URL, so entries for those names are ignored.
4573
+ */
4574
+ credentialAllowedOrigins: z19.record(z19.string(), z19.array(HttpsOriginSchema)).optional(),
4575
+ /**
4576
+ * Explicit user override of the project's type (an id from the bundled
4577
+ * project-type taxonomy, see `project-types/taxonomy.ts`). When set, it
4578
+ * wins over `detectedProjectType` for craftbook suggestions. Cleared
4579
+ * (unset) → fall back to auto-detection. Missing on every project written
4580
+ * before this field existed.
4581
+ */
4582
+ projectTypeId: z19.string().optional(),
4583
+ /**
4584
+ * Auto-detected project type, recomputed on each content-index scan from
4585
+ * the workspace file mix + about/mission text. Not user-editable — the
4586
+ * user expresses an override via `projectTypeId`. Absent until the first
4587
+ * scan classifies the project (or when nothing scores above the floor).
4588
+ */
4589
+ detectedProjectType: z19.object({
4590
+ id: z19.string(),
4591
+ score: z19.number(),
4592
+ scannedAt: z19.string()
4593
+ }).optional(),
4594
+ /**
4595
+ * Provenance of an applied custom project type (see docs/project-types.md).
4596
+ * Distinct from `projectTypeId`, which is the taxonomy id used for craftbook
4597
+ * suggestion: a custom type stamps this on adoption and, when it `extends` a
4598
+ * taxonomy id, ALSO sets `projectTypeId` so detection-based suggestion
4599
+ * inherits. Absent on projects created without a custom type.
4600
+ */
4601
+ projectType: ProjectTypeProvenanceSchema.optional(),
4602
+ /**
4603
+ * Filesystem ownership boundary. Missing/`user` is ordinary per-account
4604
+ * state. `machine-shared` is a grandfathered project mounted from the
4605
+ * installer-managed shared root and operated on by this user's daemon.
4606
+ * The engine broker never opens the project or receives its paths.
4607
+ */
4608
+ storageScope: z19.enum(["user", "machine-shared"]).optional(),
4609
+ createdAt: z19.string(),
4610
+ updatedAt: z19.string()
4611
+ });
4612
+ function resolveProjectTypeId(project) {
4613
+ return project.projectTypeId ?? project.detectedProjectType?.id;
4614
+ }
4615
+ function projectAllowsAmbientWork(project) {
4616
+ const status = project.status ?? "active";
4617
+ return status === "active";
4618
+ }
4619
+ var InstalledPackageSchema = z19.object({
4620
+ name: z19.string(),
4621
+ version: z19.string()
4622
+ });
4623
+ var ProjectDetailSchema = ProjectSchema.extend({
4624
+ packages: z19.array(InstalledPackageSchema),
4625
+ /** Contents of `documents/about.md` inside the project, if present. */
4626
+ about: z19.string().optional(),
4627
+ /** Contents of `documents/missionObjectives.md`, if present. */
4628
+ missionObjectives: z19.string().optional()
4629
+ });
4630
+ var ProjectFileEntrySchema = z19.object({
4631
+ name: z19.string(),
4632
+ path: z19.string(),
4633
+ isDirectory: z19.boolean(),
4634
+ /** File mtime (ms epoch). Populated only when the caller opts into stats. */
4635
+ mtimeMs: z19.number().optional()
4636
+ });
4637
+ var ProjectGithubSchema = ProjectGitHubSchema;
4638
+
4639
+ // src/schemas/craftbook-test.ts
4640
+ var CRAFTBOOK_TEST_SCHEMA_VERSION = 1;
4641
+ var CRAFTBOOK_TEST_FILENAME = "test.json";
4642
+ var PrometheusAlertsCheckSchema = z20.object({
4643
+ kind: z20.literal("prometheusAlerts"),
4644
+ file: z20.string().min(1),
4645
+ minRules: z20.number().int().positive().optional(),
4646
+ maxPageAlerts: z20.number().int().nonnegative().optional(),
4647
+ allowedSeverities: z20.array(z20.string().min(1)).optional(),
4648
+ requiredServices: z20.array(z20.string().min(1)).optional(),
4649
+ requiredRunbookUrls: z20.array(z20.string().min(1)).optional()
4650
+ }).strict();
4651
+ var NodeScriptPassesCheckSchema = z20.object({
4652
+ kind: z20.literal("nodeScriptPasses"),
4653
+ script: z20.string().min(1),
4654
+ timeoutMs: z20.number().int().positive().optional(),
4655
+ requiredOutput: z20.array(
4656
+ z20.object({
4657
+ pattern: z20.string().min(1),
4658
+ flags: z20.string().optional(),
4659
+ label: z20.string().optional()
4660
+ }).strict()
4661
+ ).optional()
4662
+ }).strict();
4663
+ var BinaryDocumentCheckSchema = z20.object({
4664
+ kind: z20.literal("binaryDocument"),
4665
+ file: z20.string().min(1),
4666
+ /** Look in the artifacts drawer instead of the workspace. */
4667
+ artifact: z20.boolean().optional(),
4668
+ /** Floor on the container's byte length; defaults to 1000. */
4669
+ minBytes: z20.number().int().positive().optional()
4670
+ }).strict();
4671
+ var CraftbookTestCheckSchema = z20.union([
4672
+ GateCheckSchema,
4673
+ PrometheusAlertsCheckSchema,
4674
+ NodeScriptPassesCheckSchema,
4675
+ BinaryDocumentCheckSchema
4676
+ ]);
4677
+ var MockServiceIdSchema = z20.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "mock service ids are lowercase kebab-case");
4678
+ var MockToolsetIdSchema = z20.string().min(1).regex(
4679
+ /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,
4680
+ "mock toolset ids are lowercase catalog ids or scoped npm-style ids"
4681
+ );
4682
+ var MockHttpRouteSchema = z20.object({
4683
+ method: z20.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
4684
+ /** Exact path, `:param` segments, or a trailing `*` wildcard. */
4685
+ path: z20.string().min(1),
4686
+ status: z20.number().int().min(100).max(599).default(200),
4687
+ headers: z20.record(z20.string(), z20.string()).optional(),
4688
+ /** String bodies are served verbatim; anything else is JSON-encoded. */
4689
+ body: z20.unknown(),
4690
+ latencyMs: z20.number().int().nonnegative().optional()
4691
+ }).strict();
4692
+ var MockServiceSchema = z20.discriminatedUnion("kind", [
4693
+ z20.object({
4694
+ kind: z20.literal("http"),
4695
+ id: MockServiceIdSchema,
4696
+ description: z20.string().min(1),
4697
+ /**
4698
+ * v1 mock HTTP is reachable ONLY through the `http.authed`
4699
+ * credential rail (anonymous script/browser HTTP hard-rejects
4700
+ * loopback), so a credential is required. The harness seeds it and
4701
+ * grants the mock's exact origin on the trial project.
4702
+ */
4703
+ credential: z20.object({
4704
+ name: z20.string().regex(/^mock\.[a-z0-9][a-z0-9.-]*$/, "mock credentials are named mock.<service-id>"),
4705
+ authScheme: z20.enum(["bearer", "basic"]).optional()
4706
+ }).strict(),
4707
+ routes: z20.array(MockHttpRouteSchema).min(1)
4708
+ }).strict(),
4709
+ z20.object({
4710
+ kind: z20.literal("webhook"),
4711
+ id: MockServiceIdSchema,
4712
+ description: z20.string().min(1),
4713
+ /** Receiver path; defaults to `/webhook` when omitted. */
4714
+ path: z20.string().min(1).optional()
4715
+ }).strict(),
4716
+ z20.object({
4717
+ kind: z20.literal("cli"),
4718
+ id: MockServiceIdSchema,
4719
+ description: z20.string().min(1),
4720
+ /** Fake-CLI shim seeded as a workspace file (today's dry-run pattern). */
4721
+ shim: z20.object({ path: z20.string().min(1), content: z20.string() }).strict()
4722
+ }).strict(),
4723
+ z20.object({
4724
+ kind: z20.literal("mcp"),
4725
+ id: MockServiceIdSchema,
4726
+ description: z20.string().min(1),
4727
+ /** Override the local-catalog id when the mock replaces a real dependency. */
4728
+ toolsetId: MockToolsetIdSchema.optional(),
4729
+ /**
4730
+ * Served live by the eval mock rail: each declared tool becomes a
4731
+ * real tool on a per-trial Streamable-HTTP MCP endpoint, installed
4732
+ * into the trial via a local-catalog `mock-mcp-<id>` toolset.
4733
+ * `resultTemplate` is JSON-encoded as the tool result text
4734
+ * (default `{"ok":true}` when absent).
4735
+ */
4736
+ tools: z20.array(
4737
+ z20.object({
4738
+ name: z20.string().min(1),
4739
+ description: z20.string().min(1),
4740
+ resultTemplate: z20.unknown().optional(),
4741
+ /** Deterministic stateful responses, consumed in call order; the last repeats. */
4742
+ resultSequence: z20.array(z20.unknown()).min(1).optional(),
4743
+ /**
4744
+ * Deterministic eval-only file materialization after this
4745
+ * tool call. The fixture must match the container the
4746
+ * deliverable's extension claims — a `minimal-pptx` written
4747
+ * to a `.docx` path now fails the `binaryDocument` check on
4748
+ * content type rather than sliding through a byte floor.
4749
+ */
4750
+ writeFixture: z20.object({
4751
+ surface: z20.enum(["workspace", "artifact"]),
4752
+ pathArgument: z20.string().min(1),
4753
+ fixture: z20.enum(["minimal-pptx", "minimal-docx", "minimal-pdf", "minimal-png"])
4754
+ }).strict().optional()
4755
+ }).strict()
4756
+ ).min(1)
4757
+ }).strict()
4758
+ ]);
4759
+ var CraftbookTestFixtureFileSchema = z20.object({
4760
+ path: z20.string().min(1),
4761
+ content: z20.string(),
4762
+ /** Defaults to `workspace`; `harness` never enters the model-visible project. */
4763
+ surface: z20.enum(["workspace", "artifact", "harness"]).optional(),
4955
4764
  /**
4956
- * Advanced exact-origin bindings for toolset credentials. Built-in provider
4957
- * credentials are service-pinned and webhook credentials follow the
4958
- * configured webhook URL, so entries for those names are ignored.
4765
+ * Whether the fixture is presented to the model as source material.
4766
+ * Defaults to true; false still seeds the file for browsers and graders.
4959
4767
  */
4960
- credentialAllowedOrigins: z21.record(z21.string(), z21.array(HttpsOriginSchema)).optional(),
4768
+ modelInput: z20.boolean().optional()
4769
+ }).strict();
4770
+ var CraftbookTestWorkerSchema = z20.object({
4771
+ name: z20.string().min(1),
4772
+ role: z20.string().min(1),
4773
+ description: z20.string().optional(),
4774
+ about: z20.string().optional()
4775
+ }).strict();
4776
+ var CraftbookTestSetupSchema = z20.object({
4777
+ projectName: z20.string().min(1),
4778
+ about: z20.string().optional(),
4779
+ missionObjectives: z20.string().optional(),
4780
+ /** Reproduce project write posture before the craftbook task starts. */
4781
+ managedWorkspaceWritePolicy: ProjectManagedWorkspaceWritePolicySchema.optional(),
4782
+ files: z20.array(CraftbookTestFixtureFileSchema).default([]),
4783
+ /** Exact values supplied to the catalog craftbook's `paramSchema`. */
4784
+ craftbookParams: z20.record(z20.string(), z20.string()).optional(),
4961
4785
  /**
4962
- * Explicit user override of the project's type (an id from the bundled
4963
- * project-type taxonomy, see `project-types/taxonomy.ts`). When set, it
4964
- * wins over `detectedProjectType` for craftbook suggestions. Cleared
4965
- * (unset) → fall back to auto-detection. Missing on every project written
4966
- * before this field existed.
4786
+ * Direct execution target. When present the harness seeds this gezel
4787
+ * and sends the kickoff straight to it (measuring whether the book
4788
+ * guides the work); absent the Meester routes.
4967
4789
  */
4968
- projectTypeId: z21.string().optional(),
4790
+ worker: CraftbookTestWorkerSchema.optional()
4791
+ }).strict();
4792
+ var CraftbookTestDeliverableSchema = z20.object({
4793
+ path: z20.string().min(1),
4794
+ kind: DeliverableKindSchema,
4795
+ /** Grade the path in the project's artifacts drawer, not its workspace. */
4796
+ artifact: z20.boolean().optional(),
4797
+ minBytes: z20.number().int().positive().optional(),
4798
+ checks: z20.array(CraftbookTestCheckSchema).optional()
4799
+ }).strict();
4800
+ var CraftbookTestMockExpectationSchema = z20.object({
4801
+ /** Mock service id from `mocks[]`. */
4802
+ service: MockServiceIdSchema,
4803
+ minRequests: z20.number().int().positive().optional(),
4804
+ /** Regex sources matched against logged request paths. */
4805
+ requiredPaths: z20.array(z20.string().min(1)).optional(),
4806
+ forbiddenPaths: z20.array(z20.string().min(1)).optional(),
4969
4807
  /**
4970
- * Auto-detected project type, recomputed on each content-index scan from
4971
- * the workspace file mix + about/mission text. Not user-editable the
4972
- * user expresses an override via `projectTypeId`. Absent until the first
4973
- * scan classifies the project (or when nothing scores above the floor).
4808
+ * Exact MCP tool names that must each have been called at least once
4809
+ * on this service (`kind: 'mcp'` only). Exact names, not regexes
4810
+ * the tool roster is fully declared in the same file, so a pattern
4811
+ * buys nothing and invites drift. Cross-checked against the mock's
4812
+ * declared `tools[]` at parse time.
4974
4813
  */
4975
- detectedProjectType: z21.object({
4976
- id: z21.string(),
4977
- score: z21.number(),
4978
- scannedAt: z21.string()
4979
- }).optional(),
4814
+ requiredTools: z20.array(z20.string().min(1)).optional(),
4815
+ /** Per-MCP-tool call budgets for repeated journeys or retries. */
4816
+ toolCalls: z20.record(
4817
+ z20.string().min(1),
4818
+ z20.object({
4819
+ minCalls: z20.number().int().nonnegative().default(1),
4820
+ maxCalls: z20.number().int().nonnegative().optional()
4821
+ }).strict().refine(
4822
+ (value) => value.maxCalls === void 0 || value.minCalls <= value.maxCalls,
4823
+ "minCalls must be less than or equal to maxCalls"
4824
+ )
4825
+ ).optional()
4826
+ }).strict();
4827
+ var CraftbookTestHistoryExpectationSchema = z20.object({
4828
+ kind: HistoryEventKindSchema,
4829
+ minEntries: z20.number().int().nonnegative().default(1),
4830
+ maxEntries: z20.number().int().nonnegative().optional(),
4831
+ summaryPattern: z20.string().min(1).optional(),
4832
+ flags: z20.string().optional(),
4833
+ details: z20.record(z20.string(), z20.union([z20.string(), z20.number(), z20.boolean(), z20.null()])).optional()
4834
+ }).strict();
4835
+ var CraftbookTestSuccessSchema = z20.object({
4836
+ summary: z20.string().min(1),
4837
+ deliverables: z20.array(CraftbookTestDeliverableSchema).optional(),
4838
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4839
+ taskNotes: z20.object({
4840
+ minBytes: z20.number().int().positive().optional(),
4841
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4842
+ requireCraftbookTask: z20.boolean().optional()
4843
+ }).strict().optional(),
4844
+ taskGraph: z20.object({
4845
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4846
+ requireCraftbookTask: z20.boolean().optional(),
4847
+ /** Require the matching task to reach a terminal step (or complete). */
4848
+ requireTerminalStep: z20.boolean().optional(),
4849
+ requireDraftRef: z20.boolean().optional(),
4850
+ draft: z20.object({
4851
+ status: z20.enum(["draft", "paused", "active", "complete", "canceled"]).optional(),
4852
+ minDescriptionBytes: z20.number().int().positive().optional(),
4853
+ minOutcomes: z20.number().int().positive().optional(),
4854
+ minSteps: z20.number().int().positive().optional(),
4855
+ requireTerminalVerification: z20.boolean().optional(),
4856
+ requireGatedBuildSteps: z20.boolean().optional()
4857
+ }).strict().optional()
4858
+ }).strict().optional(),
4859
+ /** Assertions evaluated against the live mock server's request log. */
4860
+ mocks: z20.array(CraftbookTestMockExpectationSchema).optional(),
4861
+ /** Assertions evaluated against the project's append-only History log. */
4862
+ history: z20.array(CraftbookTestHistoryExpectationSchema).optional(),
4863
+ /** Workspace fixtures whose final content must equal the seeded bytes exactly. */
4864
+ unchangedFixtures: z20.array(z20.string().min(1)).optional()
4865
+ }).strict();
4866
+ var CraftbookTestRubricSchema = z20.object({
4867
+ artifact: z20.object({
4868
+ /** Workspace or artifact path the judge reads (adapter derives the basename). */
4869
+ path: z20.string().min(1),
4870
+ kind: z20.enum(["html", "markdown", "yaml", "typescript", "json", "text"])
4871
+ }).strict(),
4872
+ axes: z20.array(z20.object({ name: z20.string().min(1), description: z20.string().min(1) }).strict()).min(1),
4873
+ contextNote: z20.string().optional()
4874
+ }).strict();
4875
+ var CraftbookTestSpecSchema = z20.object({
4876
+ schemaVersion: z20.literal(CRAFTBOOK_TEST_SCHEMA_VERSION),
4877
+ title: z20.string().min(1),
4878
+ objective: z20.string().min(1),
4980
4879
  /**
4981
- * Provenance of an applied custom project type (see docs/project-types.md).
4982
- * Distinct from `projectTypeId`, which is the taxonomy id used for craftbook
4983
- * suggestion: a custom type stamps this on adoption and, when it `extends` a
4984
- * taxonomy id, ALSO sets `projectTypeId` so detection-based suggestion
4985
- * inherits. Absent on projects created without a custom type.
4880
+ * Task-class taxonomy tags (e.g. `html-game`, `corpus`, `external`).
4881
+ * The single declared source for harness selection and batch
4882
+ * planning replaces the old regex classifiers.
4986
4883
  */
4987
- projectType: ProjectTypeProvenanceSchema.optional(),
4884
+ tags: z20.array(z20.string().min(1)).default([]),
4885
+ /** Kickoff chat message the harness sends. Required — every book runs. */
4886
+ prompt: z20.string().min(1),
4887
+ setup: CraftbookTestSetupSchema,
4888
+ mocks: z20.array(MockServiceSchema).default([]),
4889
+ success: CraftbookTestSuccessSchema,
4890
+ rubric: CraftbookTestRubricSchema,
4891
+ qualityFocus: z20.array(z20.string().min(1)).default([]),
4988
4892
  /**
4989
- * Filesystem ownership boundary. Missing/`user` is ordinary per-account
4990
- * state. `machine-shared` is a grandfathered project mounted from the
4991
- * installer-managed shared root and operated on by this user's daemon.
4992
- * The engine broker never opens the project or receives its paths.
4893
+ * Sanctioned escape hatch for experiments carried opaquely, never
4894
+ * interpreted by CI. Promote a field out of here before relying on it.
4993
4895
  */
4994
- storageScope: z21.enum(["user", "machine-shared"]).optional(),
4995
- createdAt: z21.string(),
4996
- updatedAt: z21.string()
4896
+ extensions: z20.record(z20.string(), z20.unknown()).optional()
4897
+ }).strict().superRefine((spec, ctx) => {
4898
+ const mockIds = new Set(spec.mocks.map((m) => m.id));
4899
+ const mockById = new Map(spec.mocks.map((m) => [m.id, m]));
4900
+ for (const [i, expectation] of (spec.success.mocks ?? []).entries()) {
4901
+ if (!mockIds.has(expectation.service)) {
4902
+ ctx.addIssue({
4903
+ code: z20.ZodIssueCode.custom,
4904
+ path: ["success", "mocks", i, "service"],
4905
+ message: `success.mocks[${i}] references unknown mock service "${expectation.service}"`
4906
+ });
4907
+ }
4908
+ if (expectation.requiredTools && expectation.requiredTools.length > 0) {
4909
+ const target = mockById.get(expectation.service);
4910
+ if (target && target.kind !== "mcp") {
4911
+ ctx.addIssue({
4912
+ code: z20.ZodIssueCode.custom,
4913
+ path: ["success", "mocks", i, "requiredTools"],
4914
+ message: `success.mocks[${i}].requiredTools requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4915
+ });
4916
+ } else if (target?.kind === "mcp") {
4917
+ const declared = new Set(target.tools.map((tool) => tool.name));
4918
+ for (const name of expectation.requiredTools) {
4919
+ if (!declared.has(name)) {
4920
+ ctx.addIssue({
4921
+ code: z20.ZodIssueCode.custom,
4922
+ path: ["success", "mocks", i, "requiredTools"],
4923
+ message: `success.mocks[${i}].requiredTools names undeclared tool "${name}" on mcp service "${expectation.service}"`
4924
+ });
4925
+ }
4926
+ }
4927
+ }
4928
+ }
4929
+ if (expectation.toolCalls && Object.keys(expectation.toolCalls).length > 0) {
4930
+ const target = mockById.get(expectation.service);
4931
+ if (target && target.kind !== "mcp") {
4932
+ ctx.addIssue({
4933
+ code: z20.ZodIssueCode.custom,
4934
+ path: ["success", "mocks", i, "toolCalls"],
4935
+ message: `success.mocks[${i}].toolCalls requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4936
+ });
4937
+ } else if (target?.kind === "mcp") {
4938
+ const declared = new Set(target.tools.map((tool) => tool.name));
4939
+ for (const name of Object.keys(expectation.toolCalls)) {
4940
+ if (!declared.has(name)) {
4941
+ ctx.addIssue({
4942
+ code: z20.ZodIssueCode.custom,
4943
+ path: ["success", "mocks", i, "toolCalls", name],
4944
+ message: `success.mocks[${i}].toolCalls names undeclared tool "${name}" on mcp service "${expectation.service}"`
4945
+ });
4946
+ }
4947
+ }
4948
+ }
4949
+ }
4950
+ }
4951
+ for (const [i, mock] of spec.mocks.entries()) {
4952
+ if (mock.kind === "http" && mock.credential.name !== `mock.${mock.id}`) {
4953
+ ctx.addIssue({
4954
+ code: z20.ZodIssueCode.custom,
4955
+ path: ["mocks", i, "credential", "name"],
4956
+ message: `http mock "${mock.id}" must use credential name "mock.${mock.id}"`
4957
+ });
4958
+ }
4959
+ }
4960
+ const workspaceFixtures = new Set(
4961
+ spec.setup.files.filter((file) => file.surface === void 0 || file.surface === "workspace").map((file) => file.path)
4962
+ );
4963
+ for (const [i, path] of (spec.success.unchangedFixtures ?? []).entries()) {
4964
+ if (!workspaceFixtures.has(path)) {
4965
+ ctx.addIssue({
4966
+ code: z20.ZodIssueCode.custom,
4967
+ path: ["success", "unchangedFixtures", i],
4968
+ message: `unchanged fixture "${path}" is not a seeded workspace file`
4969
+ });
4970
+ }
4971
+ }
4972
+ for (const [i, expectation] of (spec.success.history ?? []).entries()) {
4973
+ if (expectation.maxEntries !== void 0 && expectation.minEntries > expectation.maxEntries) {
4974
+ ctx.addIssue({
4975
+ code: z20.ZodIssueCode.custom,
4976
+ path: ["success", "history", i],
4977
+ message: "minEntries must be less than or equal to maxEntries"
4978
+ });
4979
+ }
4980
+ }
4997
4981
  });
4998
- function resolveProjectTypeId(project) {
4999
- return project.projectTypeId ?? project.detectedProjectType?.id;
4982
+ function parseCraftbookTestSpec(raw, opts) {
4983
+ const mode = opts?.mode ?? "strict";
4984
+ const candidate = mode === "tolerant" ? deepStripUnknown(raw) : raw;
4985
+ const parsed = CraftbookTestSpecSchema.safeParse(candidate);
4986
+ if (parsed.success) return { ok: true, spec: parsed.data };
4987
+ return {
4988
+ ok: false,
4989
+ errors: parsed.error.issues.map((issue) => {
4990
+ const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
4991
+ return `${path}: ${issue.message}`;
4992
+ })
4993
+ };
5000
4994
  }
5001
- function projectAllowsAmbientWork(project) {
5002
- const status = project.status ?? "active";
5003
- return status === "active";
4995
+ function deepStripUnknown(raw) {
4996
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
4997
+ const record = structuredClone(raw);
4998
+ const version = record.schemaVersion;
4999
+ if (typeof version === "number" && Number.isInteger(version) && version > CRAFTBOOK_TEST_SCHEMA_VERSION) {
5000
+ record.schemaVersion = CRAFTBOOK_TEST_SCHEMA_VERSION;
5001
+ }
5002
+ for (let pass = 0; pass < 16; pass++) {
5003
+ const attempt = CraftbookTestSpecSchema.safeParse(record);
5004
+ if (attempt.success) return record;
5005
+ const unknownKeyIssues = attempt.error.issues.filter(
5006
+ (issue) => issue.code === z20.ZodIssueCode.unrecognized_keys
5007
+ );
5008
+ if (unknownKeyIssues.length === 0) return record;
5009
+ for (const issue of unknownKeyIssues) {
5010
+ const target = resolvePath(record, issue.path);
5011
+ if (target && typeof target === "object" && !Array.isArray(target)) {
5012
+ for (const key of issue.keys) delete target[key];
5013
+ }
5014
+ }
5015
+ }
5016
+ return record;
5017
+ }
5018
+ function resolvePath(root, path) {
5019
+ let node = root;
5020
+ for (const segment of path) {
5021
+ if (node === null || typeof node !== "object") return void 0;
5022
+ node = node[segment];
5023
+ }
5024
+ return node;
5004
5025
  }
5005
- var InstalledPackageSchema = z21.object({
5006
- name: z21.string(),
5007
- version: z21.string()
5026
+
5027
+ // src/schemas/codex-setup.ts
5028
+ import { z as z21 } from "zod";
5029
+ var CodexSetupModelOptionSchema = z21.object({
5030
+ id: z21.string().min(1),
5031
+ label: z21.string().min(1),
5032
+ description: z21.string().optional(),
5033
+ kind: z21.enum(["gezel", "model"]).default("model"),
5034
+ provider: z21.string().min(1),
5035
+ /** Stable gezel id for persona-backed entries. Absent on raw-model entries. */
5036
+ gezelId: z21.string().min(1).optional(),
5037
+ role: z21.string().min(1).optional(),
5038
+ /** Human-readable name of the effective inference model behind a gezel. */
5039
+ modelLabel: z21.string().min(1).optional(),
5040
+ contextWindow: z21.number().int().positive().optional(),
5041
+ supportsReasoning: z21.boolean().optional(),
5042
+ supportsTools: z21.boolean().optional()
5008
5043
  });
5009
- var ProjectDetailSchema = ProjectSchema.extend({
5010
- packages: z21.array(InstalledPackageSchema),
5011
- /** Contents of `documents/about.md` inside the project, if present. */
5012
- about: z21.string().optional(),
5013
- /** Contents of `documents/missionObjectives.md`, if present. */
5014
- missionObjectives: z21.string().optional()
5044
+ var CodexSetupStateSchema = z21.enum([
5045
+ "not-configured",
5046
+ "configured",
5047
+ "update-needed",
5048
+ "conflict",
5049
+ "unavailable"
5050
+ ]);
5051
+ var CodexSetupStatusResponseSchema = z21.object({
5052
+ state: CodexSetupStateSchema,
5053
+ models: z21.array(CodexSetupModelOptionSchema),
5054
+ configuredModel: z21.string().optional(),
5055
+ recommendedModel: z21.string().optional(),
5056
+ reasons: z21.array(z21.string()),
5057
+ message: z21.string().optional(),
5058
+ codexInstalled: z21.boolean(),
5059
+ codexVersion: z21.string().optional(),
5060
+ codexPath: z21.string().optional(),
5061
+ endpointsEnabled: z21.boolean(),
5062
+ profileName: z21.string().min(1),
5063
+ profilePath: z21.string().min(1),
5064
+ launchCommand: z21.string().min(1),
5065
+ bridge: z21.object({
5066
+ baseUrl: z21.string().url(),
5067
+ listening: z21.boolean(),
5068
+ port: z21.number().int().nonnegative()
5069
+ }),
5070
+ canConfigure: z21.boolean(),
5071
+ /** Whether Gezel-owned credential/state material exists and can be safely removed. */
5072
+ canRemove: z21.boolean()
5015
5073
  });
5016
- var ProjectFileEntrySchema = z21.object({
5017
- name: z21.string(),
5018
- path: z21.string(),
5019
- isDirectory: z21.boolean(),
5020
- /** File mtime (ms epoch). Populated only when the caller opts into stats. */
5021
- mtimeMs: z21.number().optional()
5074
+ var ConfigureCodexRequestSchema = z21.object({
5075
+ model: z21.string().min(1)
5022
5076
  });
5023
- var ProjectGithubSchema = ProjectGitHubSchema;
5024
5077
 
5025
5078
  // src/schemas/project-local.ts
5026
5079
  import { z as z22 } from "zod";
@@ -5532,6 +5585,8 @@ var UpdateTaskStepRequestSchema = z23.object({
5532
5585
  /** Step automation hooks (single ref or ordered list). `null` detaches. */
5533
5586
  onEnter: ScriptRefListSchema.nullable().optional(),
5534
5587
  onExit: ScriptRefListSchema.nullable().optional(),
5588
+ /** Required file inputs for the step. `null` clears the declaration. */
5589
+ consumes: z23.array(CraftbookStepInputSchema).min(1).nullable().optional(),
5535
5590
  /** Auto-advance contract. `null` clears it. */
5536
5591
  advanceWhen: AdvanceWhenSchema.nullable().optional(),
5537
5592
  /** The end-of-step gate (current or legacy shape). `null` clears it. */
@@ -6461,6 +6516,13 @@ var ConnectorActionSchema = z26.object({
6461
6516
  /** Consent gate this action clears at commit time (e.g. `recipient-allowlist`). */
6462
6517
  consentScope: z26.string()
6463
6518
  });
6519
+ var ConnectorSetupInstructionsSchema = z26.object({
6520
+ title: z26.string().min(1),
6521
+ description: z26.string().min(1).optional(),
6522
+ steps: z26.array(z26.string().min(1)).optional(),
6523
+ url: z26.string().url().optional(),
6524
+ urlLabel: z26.string().min(1).optional()
6525
+ });
6464
6526
  var ConnectorTypeCompositionShape = {
6465
6527
  /** Which driver executes the fetch. */
6466
6528
  driver: ConnectorDriverSchema,
@@ -6468,6 +6530,8 @@ var ConnectorTypeCompositionShape = {
6468
6530
  configSchema: z26.record(z26.string(), z26.unknown()).optional(),
6469
6531
  /** Shape of the credential the binding stores in the SecretStore. */
6470
6532
  secretShape: z26.record(z26.string(), z26.unknown()).optional(),
6533
+ /** Concise setup guidance rendered above the binding form. */
6534
+ setupInstructions: ConnectorSetupInstructionsSchema.optional(),
6471
6535
  /**
6472
6536
  * Driver-specific fetch config: `{adapterId}` (native) | `{server,list,fetch}`
6473
6537
  * (mcp) | `{fetch|cli}` (script) | `{component,action,...}` (spectral).
@@ -6777,6 +6841,15 @@ var ChatModelMlxSourceSchema = z26.object({
6777
6841
  });
6778
6842
  var ChatModelIdentitySchema = IdentityCommonSchema.extend({
6779
6843
  kind: z26.literal("chat-model"),
6844
+ /**
6845
+ * Organization that created the core model weights when it differs from
6846
+ * the catalog maintainer (for example, a quant maintained by its converter).
6847
+ * Omit when `maintainer` already names the maker.
6848
+ */
6849
+ maker: z26.object({
6850
+ name: z26.string().min(1),
6851
+ url: z26.string().url().optional()
6852
+ }).optional(),
6780
6853
  parameterSize: z26.string(),
6781
6854
  supportsTools: z26.boolean(),
6782
6855
  contextWindow: z26.number().int().positive().optional(),
@@ -6874,6 +6947,11 @@ var ChatModelManifestSchema = z26.object({
6874
6947
  name: z26.string(),
6875
6948
  url: z26.string().url().optional()
6876
6949
  }),
6950
+ /** Core-model maker; present when it differs from `maintainer`. */
6951
+ maker: z26.object({
6952
+ name: z26.string().min(1),
6953
+ url: z26.string().url().optional()
6954
+ }).optional(),
6877
6955
  logo: z26.string().optional(),
6878
6956
  license: z26.string().optional(),
6879
6957
  ...LicenseMetaShape,
@@ -7685,6 +7763,18 @@ var ChatSessionSchema = z28.object({
7685
7763
  }),
7686
7764
  /** Snapshot of the gezel's about.md at session creation, for drift warnings. */
7687
7765
  aboutSnapshot: z28.string().optional(),
7766
+ /**
7767
+ * Per-session override of `config.roleBasedNameOnlyMode` ("boring mode").
7768
+ * Stamped at creation when the creating client has a fixed presentation
7769
+ * mode — the TUI always renders role-based labels, so it pins the
7770
+ * sessions it creates to `true`. This keeps the prompt (what the model
7771
+ * is told about other gezels) consistent with what that client shows;
7772
+ * without it the TUI displayed `reviewer:` while the system prompt said
7773
+ * "the voorman is Tomas", and the model naturally leaked names into
7774
+ * prose. Unset = follow the live config flag, which is what desktop
7775
+ * sessions do.
7776
+ */
7777
+ roleBasedNameOnlyMode: z28.boolean().optional(),
7688
7778
  /** Set when the last attempted resume failed — UI surfaces a banner. */
7689
7779
  resumeFailed: z28.boolean().optional(),
7690
7780
  /**
@@ -7913,7 +8003,14 @@ var CreateChatSessionRequestSchema = z28.object({
7913
8003
  projectId: z28.string().optional(),
7914
8004
  taskRef: z28.string().optional(),
7915
8005
  stepId: z28.string().optional(),
7916
- craftbookRef: z28.string().optional()
8006
+ craftbookRef: z28.string().optional(),
8007
+ /**
8008
+ * Pin the session's name-rendering mode instead of following
8009
+ * `config.roleBasedNameOnlyMode`. Passed by clients whose presentation
8010
+ * mode is fixed (the TUI) so prompt-side name rendering matches their
8011
+ * labels. See `ChatSessionSchema.roleBasedNameOnlyMode`.
8012
+ */
8013
+ roleBasedNameOnlyMode: z28.boolean().optional()
7917
8014
  });
7918
8015
  var ListChatSessionsResponseSchema = z28.object({
7919
8016
  sessions: z28.array(ChatSessionSummarySchema)
@@ -8539,13 +8636,26 @@ var GitHubPullFileSchema = z33.object({
8539
8636
  additions: z33.number().int(),
8540
8637
  deletions: z33.number().int(),
8541
8638
  changes: z33.number().int(),
8542
- /** Truncated unified diff hunk, if returned by GitHub. */
8639
+ /** Unified diff hunk, if requested and returned by GitHub. */
8543
8640
  patch: z33.string().optional(),
8641
+ /** Character count before Gezel's local patch budget was applied. */
8642
+ patchChars: z33.number().int().nonnegative().optional(),
8643
+ /** True when Gezel clipped `patch`; callers must request the file/diff directly. */
8644
+ patchTruncated: z33.boolean().optional(),
8544
8645
  /** Prior path when `status === 'renamed'`. */
8545
8646
  previousFilename: z33.string().optional()
8546
8647
  });
8547
8648
  var ListGitHubPullFilesResponseSchema = z33.object({
8548
- files: z33.array(GitHubPullFileSchema)
8649
+ files: z33.array(GitHubPullFileSchema),
8650
+ /** Total files in the PR before an optional path filter. */
8651
+ allFiles: z33.number().int().nonnegative().optional(),
8652
+ /** Total files selected by the optional path filter. */
8653
+ totalFiles: z33.number().int().nonnegative().optional(),
8654
+ offset: z33.number().int().nonnegative().optional(),
8655
+ limit: z33.number().int().positive().optional(),
8656
+ hasMore: z33.boolean().optional(),
8657
+ nextOffset: z33.number().int().nonnegative().optional(),
8658
+ includesPatch: z33.boolean().optional()
8549
8659
  });
8550
8660
  var GitHubPullCommentSchema = z33.object({
8551
8661
  id: z33.number(),
@@ -8562,7 +8672,14 @@ var ListGitHubPullCommentsResponseSchema = z33.object({
8562
8672
  });
8563
8673
  var GitHubPullDiffResponseSchema = z33.object({
8564
8674
  number: z33.number().int(),
8565
- diff: z33.string()
8675
+ diff: z33.string(),
8676
+ /** Exact changed path when this is a file-scoped diff. */
8677
+ path: z33.string().optional(),
8678
+ offset: z33.number().int().nonnegative().optional(),
8679
+ returnedChars: z33.number().int().nonnegative().optional(),
8680
+ totalChars: z33.number().int().nonnegative().optional(),
8681
+ truncated: z33.boolean().optional(),
8682
+ nextOffset: z33.number().int().nonnegative().optional()
8566
8683
  });
8567
8684
  var GitHubCreateCommentRequestSchema = z33.object({
8568
8685
  body: z33.string().min(1)
@@ -10850,6 +10967,13 @@ var GezelConfigSchema = z37.object({
10850
10967
  * Advanced.
10851
10968
  */
10852
10969
  showAdvancedFeatures: z37.boolean().optional(),
10970
+ /**
10971
+ * When `true`, very early work-in-progress surfaces are revealed in the
10972
+ * UI and CLI. This is a discoverability preference, not a security or
10973
+ * service-layer capability boundary. Defaults on in development builds
10974
+ * and off in releases; an explicit user choice always wins.
10975
+ */
10976
+ showWorkInProgressFeatures: z37.boolean().optional(),
10853
10977
  /**
10854
10978
  * Debug-only opt-in: when `true`, the service rewrites every
10855
10979
  * template-derived gezel's `about.md` back to the prose its catalog
@@ -17460,6 +17584,7 @@ var STEP_FENCE_KEYS = [
17460
17584
  "deliverable",
17461
17585
  "onEnter",
17462
17586
  "onExit",
17587
+ "consumes",
17463
17588
  "advanceWhen",
17464
17589
  "gate",
17465
17590
  "next",
@@ -18757,6 +18882,68 @@ function isMoEFromTags(tags) {
18757
18882
  });
18758
18883
  }
18759
18884
 
18885
+ // src/model-attribution.ts
18886
+ function modelAttribution(manifest) {
18887
+ const sourceOwners = [
18888
+ manifest.llamaCpp?.huggingfaceRepo,
18889
+ manifest.mlx?.huggingfaceRepo,
18890
+ manifest.ds4?.huggingfaceRepo
18891
+ ].map((repo) => repo ? huggingFaceOwnerFromRepo(repo) : null).filter((owner) => owner !== null);
18892
+ const explicitMakerOwner = manifest.maker?.url ? huggingFaceOwnerFromUrl(manifest.maker.url) : null;
18893
+ const maintainerOwner = manifest.maintainer.url ? huggingFaceOwnerFromUrl(manifest.maintainer.url) : null;
18894
+ const upstreamOwner = manifest.upstream ? huggingFaceOwnerFromUrl(manifest.upstream) : null;
18895
+ const normalizedSources = new Set(sourceOwners.map(normalizeOrganization));
18896
+ const maintainerIsSource = [manifest.maintainer.name, maintainerOwner].filter((name) => name !== null).some((name) => normalizedSources.has(normalizeOrganization(name)));
18897
+ const upstreamIsSource = upstreamOwner ? normalizedSources.has(normalizeOrganization(upstreamOwner)) : false;
18898
+ const makerFromUpstream = !manifest.maker && maintainerIsSource && upstreamOwner && !upstreamIsSource;
18899
+ const maker = manifest.maker?.name ?? (makerFromUpstream ? displayOrganizationName(upstreamOwner) : manifest.maintainer.name);
18900
+ const makerAliases = /* @__PURE__ */ new Set([normalizeOrganization(maker)]);
18901
+ if (explicitMakerOwner) makerAliases.add(normalizeOrganization(explicitMakerOwner));
18902
+ if (upstreamOwner) makerAliases.add(normalizeOrganization(upstreamOwner));
18903
+ if (!manifest.maker && !makerFromUpstream) {
18904
+ makerAliases.add(normalizeOrganization(manifest.maintainer.name));
18905
+ if (maintainerOwner) makerAliases.add(normalizeOrganization(maintainerOwner));
18906
+ }
18907
+ const customizers = [];
18908
+ const seen = /* @__PURE__ */ new Set();
18909
+ const customizerCandidates = manifest.maker ? [manifest.maintainer.name, ...sourceOwners] : sourceOwners;
18910
+ for (const owner of customizerCandidates) {
18911
+ const normalized = normalizeOrganization(owner);
18912
+ if (!normalized || makerAliases.has(normalized) || seen.has(normalized)) continue;
18913
+ seen.add(normalized);
18914
+ customizers.push(owner);
18915
+ }
18916
+ return { maker, customizers };
18917
+ }
18918
+ function displayOrganizationName(owner) {
18919
+ const knownNames = {
18920
+ "deepseek-ai": "DeepSeek",
18921
+ "zai-org": "Z.ai"
18922
+ };
18923
+ return knownNames[owner.toLowerCase()] ?? owner;
18924
+ }
18925
+ function formatModelAttribution(manifest) {
18926
+ const attribution = modelAttribution(manifest);
18927
+ return attribution.customizers.length > 0 ? `${attribution.maker}, customized by ${attribution.customizers.join(", ")}` : attribution.maker;
18928
+ }
18929
+ function huggingFaceOwnerFromRepo(repo) {
18930
+ const slash = repo.indexOf("/");
18931
+ return slash > 0 ? repo.slice(0, slash) : null;
18932
+ }
18933
+ function huggingFaceOwnerFromUrl(value) {
18934
+ try {
18935
+ const url = new URL(value);
18936
+ if (url.hostname.toLowerCase() !== "huggingface.co") return null;
18937
+ const owner = url.pathname.split("/").filter(Boolean)[0];
18938
+ return owner ?? null;
18939
+ } catch {
18940
+ return null;
18941
+ }
18942
+ }
18943
+ function normalizeOrganization(value) {
18944
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
18945
+ }
18946
+
18760
18947
  // src/fitness-badge.ts
18761
18948
  var CHECK_LABELS = [
18762
18949
  { key: "spawn", label: "did not start" },
@@ -25379,6 +25566,11 @@ function collapseCraftbookForTier(book, opts) {
25379
25566
  0,
25380
25567
  ...members.map((m) => m.gate ? normalizeStepGate(m.gate).maxAttempts ?? 0 : 0)
25381
25568
  );
25569
+ const consumes = members.flatMap((member) => member.consumes ?? []).filter(
25570
+ (input, index, all) => all.findIndex(
25571
+ (candidate) => candidate.file === input.file && Boolean(candidate.artifact) === Boolean(input.artifact)
25572
+ ) === index
25573
+ );
25382
25574
  const isLast = groupIdx === groups.length - 1;
25383
25575
  const nextGroupAnchor = groups[groupIdx + 1];
25384
25576
  const nextId = nextGroupAnchor ? nextGroupAnchor.filter((m) => hasCompletionGate(m)).slice(-1)[0] ?? nextGroupAnchor[nextGroupAnchor.length - 1] : void 0;
@@ -25394,6 +25586,7 @@ function collapseCraftbookForTier(book, opts) {
25394
25586
  const step = {
25395
25587
  ...anchor,
25396
25588
  prompt: collapsedPrompt({ anchor, mergedNames, checks }),
25589
+ ...consumes.length > 0 ? { consumes } : { consumes: void 0 },
25397
25590
  gate,
25398
25591
  // Terminal + advanceWhen is an illegal combination; the terminal
25399
25592
  // group keeps only its completion gate.
@@ -25403,7 +25596,7 @@ function collapseCraftbookForTier(book, opts) {
25403
25596
  advanceWhen: anchor.advanceWhen ? { ...anchor.advanceWhen, goto: void 0 } : void 0
25404
25597
  }
25405
25598
  };
25406
- for (const key of ["terminal", "advanceWhen", "next"]) {
25599
+ for (const key of ["terminal", "advanceWhen", "next", "consumes"]) {
25407
25600
  if (step[key] === void 0) delete step[key];
25408
25601
  }
25409
25602
  if (step.advanceWhen && step.advanceWhen.goto === void 0) {
@@ -25542,6 +25735,16 @@ function docFromCraftbook(book) {
25542
25735
  };
25543
25736
  }
25544
25737
  function augmentGraphProblem(problem, stepIds) {
25738
+ const artifactInput = /^step "([^"]+)" consumes artifact "([^"]+)" but its prompt does not explicitly call `read_artifact`$/.exec(
25739
+ problem
25740
+ );
25741
+ if (artifactInput) {
25742
+ return {
25743
+ where: `steps (id "${artifactInput[1]}") \u2192 prompt`,
25744
+ message: `${problem}.`,
25745
+ fix: `start the procedure with \`read_artifact({ path: ${JSON.stringify(artifactInput[2])} })\`; artifact paths are not workspace paths`
25746
+ };
25747
+ }
25545
25748
  const missing = /"([^"]+)" missing from steps/.exec(problem);
25546
25749
  if (missing) {
25547
25750
  const near = nearestMatch(missing[1], stepIds);
@@ -26861,9 +27064,38 @@ function deriveThreadTitleFromMessages(messages, options = {}) {
26861
27064
  return starter ? deriveThreadTitle(starter.content) : null;
26862
27065
  }
26863
27066
 
27067
+ // src/catalog-work-in-progress.ts
27068
+ var CONNECTOR_PROJECT_TYPE_BASES = /* @__PURE__ */ new Set(["email", "social-media"]);
27069
+ function resolveShowWorkInProgressFeatures(configured, buildVersion) {
27070
+ return configured ?? buildVersion === "0.0.0";
27071
+ }
27072
+ function catalogItemUsesConnectors(item, connectorCraftbookIds2 = /* @__PURE__ */ new Set()) {
27073
+ const manifest = item.manifest;
27074
+ if (manifest.kind === "craftbook-template") {
27075
+ return (manifest.connectors?.length ?? 0) > 0;
27076
+ }
27077
+ if (manifest.kind !== "project-type") return false;
27078
+ if (manifest.extends && CONNECTOR_PROJECT_TYPE_BASES.has(manifest.extends)) return true;
27079
+ if ((manifest.craftbooks ?? []).some((id) => connectorCraftbookIds2.has(id))) return true;
27080
+ return (manifest.schedules ?? []).some(
27081
+ (schedule) => connectorCraftbookIds2.has(schedule.craftbook)
27082
+ );
27083
+ }
27084
+ function connectorCraftbookIds(items) {
27085
+ return new Set(
27086
+ items.flatMap(
27087
+ (item) => item.manifest.kind === "craftbook-template" && catalogItemUsesConnectors(item) ? [item.manifest.id] : []
27088
+ )
27089
+ );
27090
+ }
27091
+ function visibleCatalogItems(items, showWorkInProgressFeatures, connectorIds = connectorCraftbookIds(items)) {
27092
+ if (showWorkInProgressFeatures) return [...items];
27093
+ return items.filter((item) => !catalogItemUsesConnectors(item, connectorIds));
27094
+ }
27095
+
26864
27096
  // src/index.ts
26865
- var GEZEL_VERSION = "1.0.2";
26866
- var GEZEL_CONTENT_COMPAT = "1.26225";
27097
+ var GEZEL_VERSION = "1.0.3";
27098
+ var GEZEL_CONTENT_COMPAT = "1.26226";
26867
27099
  function nowIso() {
26868
27100
  return (/* @__PURE__ */ new Date()).toISOString();
26869
27101
  }
@@ -26990,6 +27222,7 @@ export {
26990
27222
  CraftbookSchema,
26991
27223
  CraftbookScriptsSchema,
26992
27224
  CraftbookSpawnSchema,
27225
+ CraftbookStepInputSchema,
26993
27226
  CraftbookStepSchema,
26994
27227
  CraftbookSuggestionSchema,
26995
27228
  CraftbookSummarySchema,
@@ -27895,6 +28128,7 @@ export {
27895
28128
  blockSize,
27896
28129
  buildIssueUrl,
27897
28130
  buildSuiteScoreboard,
28131
+ catalogItemUsesConnectors,
27898
28132
  cellAttributableTrials,
27899
28133
  classifySecurityLevel,
27900
28134
  coerceDeliverableKind,
@@ -27906,6 +28140,7 @@ export {
27906
28140
  composeFileContext,
27907
28141
  composeFitnessBadge,
27908
28142
  computeModelFit,
28143
+ connectorCraftbookIds,
27909
28144
  craftbookDocFormatFromEnv,
27910
28145
  craftbookFromDoc,
27911
28146
  craftbookRequirementsMet,
@@ -27948,6 +28183,7 @@ export {
27948
28183
  formatCraftbookDocErrors,
27949
28184
  formatErrorReport,
27950
28185
  formatJsonSchemaViolations,
28186
+ formatModelAttribution,
27951
28187
  formatNpmRegistrySpec,
27952
28188
  formatPassClaim,
27953
28189
  formatReviewProvenance,
@@ -28024,6 +28260,7 @@ export {
28024
28260
  meetsCapabilityFloor,
28025
28261
  mergeStreets,
28026
28262
  metaToFormValue,
28263
+ modelAttribution,
28027
28264
  modelFitnessKey,
28028
28265
  nearestFreeRect,
28029
28266
  nearestMatch,
@@ -28098,6 +28335,7 @@ export {
28098
28335
  resolveRoleId,
28099
28336
  resolveSandboxCopilot,
28100
28337
  resolveSecurityPolicy,
28338
+ resolveShowWorkInProgressFeatures,
28101
28339
  resolveSteps,
28102
28340
  retryTransient,
28103
28341
  rewriteGezelHrefs,
@@ -28154,6 +28392,7 @@ export {
28154
28392
  validateScriptInput,
28155
28393
  verifyBinaryDocumentBytes,
28156
28394
  videoMemoryBudgetBytes,
28395
+ visibleCatalogItems,
28157
28396
  workshopTempoDefaults,
28158
28397
  writeProcessOutput,
28159
28398
  xpForLevel,