@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.
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
@@ -658,7 +673,9 @@ var GateCheckSchema = z3.discriminatedUnion("kind", [
658
673
  z3.object({
659
674
  kind: z3.literal("markdownHeadingsMatch"),
660
675
  file: z3.string().min(1),
661
- outlineFile: z3.string().min(1)
676
+ outlineFile: z3.string().min(1),
677
+ /** Read outlineFile from artifacts while file uses the check's primary surface. */
678
+ outlineArtifact: z3.boolean().optional()
662
679
  }),
663
680
  /**
664
681
  * Named facts in `file` must come from authorized sources: each fact
@@ -921,6 +938,12 @@ var AdvanceWhenSchema = z5.object({
921
938
  /** Step to activate on the signal. Defaults to `next`; must resolve like `next`. */
922
939
  goto: z5.string().optional()
923
940
  });
941
+ var CraftbookStepInputSchema = z5.object({
942
+ /** Path relative to the selected drawer's root. */
943
+ file: z5.string().min(1).describe("Path relative to the selected drawer root."),
944
+ /** Read from the artifacts drawer instead of the project workspace. */
945
+ artifact: z5.boolean().optional().describe("True for the artifacts drawer; false/omitted for the project workspace.")
946
+ });
924
947
  var ModelTierSchema = z5.enum(MODEL_TIER_ORDER);
925
948
  var CraftbookStepSchema = z5.object({
926
949
  id: z5.string().min(1),
@@ -961,6 +984,10 @@ var CraftbookStepSchema = z5.object({
961
984
  * read the LAST ref's output (legacy routing; prefer gate routing).
962
985
  */
963
986
  onExit: ScriptRefListSchema.optional(),
987
+ /** Required file inputs for this step, in the order they should be opened. */
988
+ consumes: z5.array(CraftbookStepInputSchema).min(1).optional().describe(
989
+ "Files this step must open before working. Artifact inputs also require an explicit `read_artifact` call in the step prompt."
990
+ ),
964
991
  /** See {@link AdvanceWhenSchema}. */
965
992
  advanceWhen: AdvanceWhenSchema.optional(),
966
993
  /** The end-of-step decision. See {@link StepGateSchema} (current) / {@link GateSpecSchema} (legacy). */
@@ -972,14 +999,16 @@ var CraftbookStepSchema = z5.object({
972
999
  * Marks the parent step that triggers a declarative per-item fanout
973
1000
  * (see {@link CraftbookSpawnSchema}). When this step activates on a
974
1001
  * spawn-host task, the runtime reads the craftbook's `spawn.overFile`
975
- * workspace JSON array and spawns one child task per item — no model
1002
+ * JSON array on its declared surface and spawns one child task per item — no model
976
1003
  * tool call. Inert unless the craftbook also declares `spawn`.
977
1004
  */
978
1005
  spawnFanout: z5.boolean().optional()
979
1006
  });
980
1007
  var CraftbookSpawnSchema = z5.object({
981
- /** Workspace-relative JSON file the parent produces; its array drives the fanout. */
1008
+ /** Surface-relative JSON file the parent produces; its array drives the fanout. */
982
1009
  overFile: z5.string().min(1),
1010
+ /** Read overFile from the artifacts drawer instead of the project workspace. */
1011
+ overArtifact: z5.boolean().optional(),
983
1012
  /** Dotted path to the array inside `overFile`. Absent → the file itself is the array. */
984
1013
  itemsPath: z5.string().optional(),
985
1014
  /** Entry step id of the child template. Defaults to the first `steps` entry. */
@@ -1015,6 +1044,14 @@ function validateCraftbookGraph(cb) {
1015
1044
  problems.push(`step "${s.id}" advanceWhen.goto "${s.advanceWhen.goto}" missing from steps`);
1016
1045
  }
1017
1046
  }
1047
+ for (const input of s.consumes ?? []) {
1048
+ if (!input.artifact) continue;
1049
+ if (!/`read_artifact(?:`|\()/.test(s.prompt ?? "")) {
1050
+ problems.push(
1051
+ `step "${s.id}" consumes artifact "${input.file}" but its prompt does not explicitly call \`read_artifact\``
1052
+ );
1053
+ }
1054
+ }
1018
1055
  if (s.gate) {
1019
1056
  const gate = normalizeStepGate(s.gate);
1020
1057
  if (s.terminal && gate.at === "activation") {
@@ -1307,6 +1344,9 @@ var NewCraftbookStepSchema = z5.object({
1307
1344
  assignee: TaskAssigneeSchema.optional(),
1308
1345
  onEnter: ScriptRefListSchema.optional(),
1309
1346
  onExit: ScriptRefListSchema.optional(),
1347
+ consumes: z5.array(CraftbookStepInputSchema).min(1).optional().describe(
1348
+ "Files this step must open before working. Artifact inputs also require an explicit `read_artifact` call in the step prompt."
1349
+ ),
1310
1350
  advanceWhen: AdvanceWhenSchema.optional(),
1311
1351
  gate: StepGateUnionSchema.optional(),
1312
1352
  /** See {@link StepDeliverableSchema} — one field attaches the enforced gate. */
@@ -1419,6 +1459,7 @@ function resolveSteps(blueprints) {
1419
1459
  ...s.assignee ? { assignee: s.assignee } : {},
1420
1460
  ...s.onEnter ? { onEnter: s.onEnter } : {},
1421
1461
  ...s.onExit ? { onExit: s.onExit } : {},
1462
+ ...s.consumes && s.consumes.length > 0 ? { consumes: s.consumes } : {},
1422
1463
  ...s.advanceWhen ? { advanceWhen: s.advanceWhen } : {},
1423
1464
  ...s.gate ? { gate: s.gate } : {},
1424
1465
  ...s.next ? { next: s.next } : {},
@@ -1541,6 +1582,10 @@ function applyStepPatch(step, patch) {
1541
1582
  delete updated.onExit;
1542
1583
  } else updated.onExit = patch.onExit;
1543
1584
  }
1585
+ if (patch.consumes !== void 0) {
1586
+ if (patch.consumes === null || patch.consumes.length === 0) delete updated.consumes;
1587
+ else updated.consumes = patch.consumes;
1588
+ }
1544
1589
  if (patch.advanceWhen !== void 0) {
1545
1590
  if (patch.advanceWhen === null) delete updated.advanceWhen;
1546
1591
  else updated.advanceWhen = patch.advanceWhen;
@@ -1719,7 +1764,7 @@ function craftbookDocFormatFromEnv(value) {
1719
1764
  }
1720
1765
 
1721
1766
  // src/schemas/craftbook-test.ts
1722
- import { z as z19 } from "zod";
1767
+ import { z as z20 } from "zod";
1723
1768
 
1724
1769
  // src/schemas/history.ts
1725
1770
  import { z as z18 } from "zod";
@@ -3701,7 +3746,15 @@ var ChatEventSchema = z17.discriminatedUnion("type", [
3701
3746
  kind: z17.string(),
3702
3747
  summary: z17.string(),
3703
3748
  at: z17.string(),
3704
- taskRef: z17.string().optional()
3749
+ taskRef: z17.string().optional(),
3750
+ /**
3751
+ * Gezel responsible for the event, when History recorded one. Kept
3752
+ * separate from the human-readable summary so fixed-presentation
3753
+ * clients (notably the role-name-only CLI) can render the actor using
3754
+ * their own naming mode instead of leaking the friendly name embedded
3755
+ * in the audit prose.
3756
+ */
3757
+ gezelId: z17.string().optional()
3705
3758
  }),
3706
3759
  /**
3707
3760
  * Emitted when a gezel crosses a growth level threshold and a pending
@@ -4242,608 +4295,173 @@ var ListHistoryResponseSchema = z18.object({
4242
4295
  entries: z18.array(HistoryEntrySchema)
4243
4296
  });
4244
4297
 
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"
4298
+ // src/schemas/project.ts
4299
+ import { z as z19 } from "zod";
4300
+ var HttpsOriginSchema = z19.string().url().refine(
4301
+ (value) => {
4302
+ try {
4303
+ const url = new URL(value);
4304
+ return url.protocol === "https:" && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash;
4305
+ } catch {
4306
+ return false;
4307
+ }
4308
+ },
4309
+ { message: "must be an exact HTTPS origin (for example https://api.example.com)" }
4287
4310
  );
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(),
4311
+ var ProjectGitHubSchema = z19.object({
4312
+ // Accept any non-empty string. Git URLs come in many forms beyond
4313
+ // `https://`: ssh shorthand (`git@github.com:owner/repo`), local
4314
+ // filesystem paths (`/path/to/bare.git`), other-host references.
4315
+ // The strict `.url()` validator rejected the filesystem-path form
4316
+ // and broke Phase-3 worktree tests that use a local bare repo as
4317
+ // the upstream. Higher layers (the github sync code) parse the URL
4318
+ // and reject anything that doesn't shape up as a clonable ref.
4319
+ url: z19.string().min(1),
4320
+ branch: z19.string().optional(),
4321
+ /** Resolved absolute path to the working tree. Managed by the service. */
4322
+ checkoutDir: z19.string().optional(),
4323
+ lastSyncedAt: z19.string().optional(),
4324
+ /** Repo default branch (e.g. "main"), detected lazily and cached. Managed by the service. */
4325
+ defaultBranch: z19.string().optional()
4326
+ });
4327
+ var ProjectConnectorBindingSchema = z19.object({
4328
+ /** Stable binding id; also the SecretStore `fieldId` + corpus-slug seed. */
4329
+ id: z19.string().min(1),
4330
+ /** The connector-type catalog id, e.g. `mail-gmail`, `linear-issues`. */
4331
+ type: z19.string().min(1),
4332
+ /** Catalog source the type resolved from (provenance/pin). */
4333
+ sourceId: z19.string().optional(),
4334
+ /** Pinned connector-type version. */
4335
+ version: z19.string().optional(),
4336
+ displayName: z19.string().optional(),
4370
4337
  /**
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.
4338
+ * Artifact-relative corpus root (`data/<corpusName>`), resolved once at bind
4339
+ * time and never recomputed renaming a binding must not strand its corpus.
4340
+ */
4341
+ corpusDir: z19.string().optional(),
4342
+ /** Per-binding config, validated at bind time against the type's `configSchema`. */
4343
+ config: z19.record(z19.string(), z19.unknown()).default({}),
4344
+ /** Opaque, adapter-shaped incremental-sync cursor. Persisted so resync resumes. */
4345
+ cursor: z19.unknown().optional(),
4346
+ /** Pause syncing without unbinding. */
4347
+ disabled: z19.boolean().optional(),
4348
+ lastSyncedAt: z19.string().optional(),
4349
+ /** Last sync error, surfaced in the UI; cleared on the next success. */
4350
+ lastError: z19.string().optional()
4351
+ });
4352
+ var ProjectNudgeConfigSchema = z19.object({
4353
+ enabled: z19.boolean().optional(),
4354
+ rapidIntervalMs: z19.number().int().positive().optional(),
4355
+ slowIntervalMs: z19.number().int().positive().optional(),
4356
+ recentActivityWindowMs: z19.number().int().positive().optional(),
4357
+ rapidAttemptsBeforeBackoff: z19.number().int().positive().optional(),
4358
+ /**
4359
+ * Grace period applied to the very first nudge a project ever
4360
+ * receives, measured from `project.createdAt`. Default per tempo;
4361
+ * setting `0` opts out (legacy behavior — first nudge fires as
4362
+ * soon as the rapid interval allows).
4373
4363
  */
4374
- modelInput: z19.boolean().optional()
4364
+ firstNudgeGraceMs: z19.number().int().nonnegative().optional()
4365
+ });
4366
+ var ProjectNudgeStateSchema = z19.object({
4367
+ lastNudgedAt: z19.string().optional(),
4368
+ consecutiveRapidNudges: z19.number().int().nonnegative().optional()
4369
+ });
4370
+ var ProjectTabVisibilitySchema = z19.object({
4371
+ overview: z19.boolean().optional(),
4372
+ tasks: z19.boolean().optional(),
4373
+ approvals: z19.boolean().optional(),
4374
+ workspace: z19.boolean().optional(),
4375
+ artifacts: z19.boolean().optional(),
4376
+ map: z19.boolean().optional()
4375
4377
  }).strict();
4376
- var CraftbookTestWorkerSchema = z19.object({
4377
- name: z19.string().min(1),
4378
- role: z19.string().min(1),
4378
+ var ProjectManagedWorkspaceWritePolicySchema = z19.enum(["auto", "allow", "deny"]);
4379
+ var ProjectTypeProvenanceSchema = z19.object({
4380
+ /** Catalog id of the applied project type. */
4381
+ id: z19.string(),
4382
+ /** Type version installed at adoption. */
4383
+ version: z19.string(),
4384
+ /** Catalog source the type resolved from (`bundled` | `local` | `community` | …). */
4385
+ source: z19.string(),
4386
+ /** Param values collected at adoption, substituted into templates + seed files. */
4387
+ params: z19.record(z19.string(), z19.unknown()).optional(),
4388
+ /** ISO timestamp of adoption. */
4389
+ appliedAt: z19.string()
4390
+ });
4391
+ var ProjectSchema = z19.object({
4392
+ id: EntityIdSchema,
4393
+ name: z19.string(),
4379
4394
  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(),
4395
+ workingDir: z19.string().optional(),
4396
+ /** Optional gezel that acts as the project's voorman (foreman). Surfaces in
4397
+ * the project detail pane, flows into the system prompt when a session is
4398
+ * scoped here. For solo projects (`mode === 'solo'`) this same field
4399
+ * holds the project's Builder — the data is unchanged, only the
4400
+ * label flips. */
4401
+ voormanGezelId: z19.string().optional(),
4389
4402
  /**
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.
4403
+ * Internal marker: ISO timestamp of the one-time automatic voorman
4404
+ * assignment. The indexer ensures every project ends up with a voorman
4405
+ * adopting the `@project` gezel minted from an AGENTS.md/CLAUDE.md, or
4406
+ * (when there's no instruction file) promoting an existing roster gezel.
4407
+ * Set once a voorman has been ensured so a later *manual* clear isn't
4408
+ * silently re-populated on the next scan. Not user-editable; absent on
4409
+ * projects last written before this field existed.
4393
4410
  */
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(),
4411
+ voormanAutoAssignedAt: z19.string().optional(),
4409
4412
  /**
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.
4413
+ * Roster gezels that have been pulled into this project. Populated
4414
+ * automatically the first time a gezel is set as voorman, opens a
4415
+ * session scoped here, is pinged via `message_gezel` / `ask_gezel`,
4416
+ * or is assigned to a task; can also be edited explicitly via the
4417
+ * `add_gezel_to_project` / `remove_gezel_from_project` MCP tools.
4418
+ *
4419
+ * Advisory: not used to gate access — any gezel can still chat or be
4420
+ * assigned to a task here; the roster is for "team" UX surfacing,
4421
+ * notification scoping, and tracking who actually does work in the
4422
+ * project. Order is not significant. Missing on disk → empty roster
4423
+ * (back-compat with every project written before this field
4424
+ * existed).
4415
4425
  */
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),
4426
+ gezelIds: z19.array(z19.string()).optional(),
4481
4427
  /**
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.
4428
+ * Suggested-work keys the user has dismissed ("don't offer this again
4429
+ * here"). Advisory UI state, same spirit as `gezelIds`: enabling a
4430
+ * dismissed key un-dismisses it, and a materialized host always
4431
+ * outranks a dismissal. Keys are the stable suggested-work identities
4432
+ * (`gezel-template:<templateId>:<craftbookId>[#N]` /
4433
+ * `project-type:<typeId>:<scheduleKey>`). Deliberately not exposed
4434
+ * through the model-facing `update_project` MCP tool.
4485
4435
  */
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([]),
4436
+ suggestedWorkDismissed: z19.array(z19.string()).optional(),
4494
4437
  /**
4495
- * Sanctioned escape hatch for experiments carried opaquely, never
4496
- * interpreted by CI. Promote a field out of here before relying on it.
4438
+ * Shared per-project configuration values ("project properties") that
4439
+ * craftbook params and features draw from e.g. `content.language`,
4440
+ * the designated language a translator gezel targets. Keys are ids
4441
+ * from the well-known registry in `project-properties.ts`, but unknown
4442
+ * ids are allowed (the registry improves display, it doesn't gate).
4443
+ * Values are plain strings; empty string is treated as unset.
4497
4444
  */
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
- // src/schemas/project.ts
4681
- import { z as z21 } from "zod";
4682
- var HttpsOriginSchema = z21.string().url().refine(
4683
- (value) => {
4684
- try {
4685
- const url = new URL(value);
4686
- return url.protocol === "https:" && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash;
4687
- } catch {
4688
- return false;
4689
- }
4690
- },
4691
- { message: "must be an exact HTTPS origin (for example https://api.example.com)" }
4692
- );
4693
- var ProjectGitHubSchema = z21.object({
4694
- // Accept any non-empty string. Git URLs come in many forms beyond
4695
- // `https://`: ssh shorthand (`git@github.com:owner/repo`), local
4696
- // filesystem paths (`/path/to/bare.git`), other-host references.
4697
- // The strict `.url()` validator rejected the filesystem-path form
4698
- // and broke Phase-3 worktree tests that use a local bare repo as
4699
- // the upstream. Higher layers (the github sync code) parse the URL
4700
- // and reject anything that doesn't shape up as a clonable ref.
4701
- url: z21.string().min(1),
4702
- branch: z21.string().optional(),
4703
- /** Resolved absolute path to the working tree. Managed by the service. */
4704
- checkoutDir: z21.string().optional(),
4705
- lastSyncedAt: z21.string().optional(),
4706
- /** Repo default branch (e.g. "main"), detected lazily and cached. Managed by the service. */
4707
- defaultBranch: z21.string().optional()
4708
- });
4709
- var ProjectConnectorBindingSchema = z21.object({
4710
- /** Stable binding id; also the SecretStore `fieldId` + corpus-slug seed. */
4711
- id: z21.string().min(1),
4712
- /** The connector-type catalog id, e.g. `mail-gmail`, `linear-issues`. */
4713
- type: z21.string().min(1),
4714
- /** Catalog source the type resolved from (provenance/pin). */
4715
- sourceId: z21.string().optional(),
4716
- /** Pinned connector-type version. */
4717
- version: z21.string().optional(),
4718
- displayName: z21.string().optional(),
4719
- /**
4720
- * Artifact-relative corpus root (`data/<corpusName>`), resolved once at bind
4721
- * time and never recomputed — renaming a binding must not strand its corpus.
4722
- */
4723
- corpusDir: z21.string().optional(),
4724
- /** Per-binding config, validated at bind time against the type's `configSchema`. */
4725
- config: z21.record(z21.string(), z21.unknown()).default({}),
4726
- /** Opaque, adapter-shaped incremental-sync cursor. Persisted so resync resumes. */
4727
- cursor: z21.unknown().optional(),
4728
- /** Pause syncing without unbinding. */
4729
- disabled: z21.boolean().optional(),
4730
- lastSyncedAt: z21.string().optional(),
4731
- /** Last sync error, surfaced in the UI; cleared on the next success. */
4732
- lastError: z21.string().optional()
4733
- });
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(),
4740
- /**
4741
- * Grace period applied to the very first nudge a project ever
4742
- * receives, measured from `project.createdAt`. Default per tempo;
4743
- * setting `0` opts out (legacy behavior — first nudge fires as
4744
- * soon as the rapid interval allows).
4745
- */
4746
- firstNudgeGraceMs: z21.number().int().nonnegative().optional()
4747
- });
4748
- var ProjectNudgeStateSchema = z21.object({
4749
- lastNudgedAt: z21.string().optional(),
4750
- consecutiveRapidNudges: z21.number().int().nonnegative().optional()
4751
- });
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()
4759
- }).strict();
4760
- var ProjectManagedWorkspaceWritePolicySchema = z21.enum(["auto", "allow", "deny"]);
4761
- var ProjectTypeProvenanceSchema = z21.object({
4762
- /** Catalog id of the applied project type. */
4763
- id: z21.string(),
4764
- /** Type version installed at adoption. */
4765
- version: z21.string(),
4766
- /** Catalog source the type resolved from (`bundled` | `local` | `community` | …). */
4767
- source: z21.string(),
4768
- /** Param values collected at adoption, substituted into templates + seed files. */
4769
- params: z21.record(z21.string(), z21.unknown()).optional(),
4770
- /** ISO timestamp of adoption. */
4771
- appliedAt: z21.string()
4772
- });
4773
- var ProjectSchema = z21.object({
4774
- id: EntityIdSchema,
4775
- name: z21.string(),
4776
- description: z21.string().optional(),
4777
- workingDir: z21.string().optional(),
4778
- /** Optional gezel that acts as the project's voorman (foreman). Surfaces in
4779
- * the project detail pane, flows into the system prompt when a session is
4780
- * scoped here. For solo projects (`mode === 'solo'`) this same field
4781
- * holds the project's ambachtsman — the data is unchanged, only the
4782
- * label flips. */
4783
- voormanGezelId: z21.string().optional(),
4784
- /**
4785
- * Internal marker: ISO timestamp of the one-time automatic voorman
4786
- * assignment. The indexer ensures every project ends up with a voorman
4787
- * — adopting the `@project` gezel minted from an AGENTS.md/CLAUDE.md, or
4788
- * (when there's no instruction file) promoting an existing roster gezel.
4789
- * Set once a voorman has been ensured so a later *manual* clear isn't
4790
- * silently re-populated on the next scan. Not user-editable; absent on
4791
- * projects last written before this field existed.
4792
- */
4793
- voormanAutoAssignedAt: z21.string().optional(),
4794
- /**
4795
- * Roster — gezels that have been pulled into this project. Populated
4796
- * automatically the first time a gezel is set as voorman, opens a
4797
- * session scoped here, is pinged via `message_gezel` / `ask_gezel`,
4798
- * or is assigned to a task; can also be edited explicitly via the
4799
- * `add_gezel_to_project` / `remove_gezel_from_project` MCP tools.
4800
- *
4801
- * Advisory: not used to gate access — any gezel can still chat or be
4802
- * assigned to a task here; the roster is for "team" UX surfacing,
4803
- * notification scoping, and tracking who actually does work in the
4804
- * project. Order is not significant. Missing on disk → empty roster
4805
- * (back-compat with every project written before this field
4806
- * existed).
4807
- */
4808
- gezelIds: z21.array(z21.string()).optional(),
4809
- /**
4810
- * Suggested-work keys the user has dismissed ("don't offer this again
4811
- * here"). Advisory UI state, same spirit as `gezelIds`: enabling a
4812
- * dismissed key un-dismisses it, and a materialized host always
4813
- * outranks a dismissal. Keys are the stable suggested-work identities
4814
- * (`gezel-template:<templateId>:<craftbookId>[#N]` /
4815
- * `project-type:<typeId>:<scheduleKey>`). Deliberately not exposed
4816
- * through the model-facing `update_project` MCP tool.
4817
- */
4818
- suggestedWorkDismissed: z21.array(z21.string()).optional(),
4819
- /**
4820
- * Shared per-project configuration values ("project properties") that
4821
- * craftbook params and features draw from — e.g. `content.language`,
4822
- * the designated language a translator gezel targets. Keys are ids
4823
- * from the well-known registry in `project-properties.ts`, but unknown
4824
- * ids are allowed (the registry improves display, it doesn't gate).
4825
- * Values are plain strings; empty string is treated as unset.
4826
- */
4827
- properties: z21.record(z21.string(), z21.string()).optional(),
4445
+ properties: z19.record(z19.string(), z19.string()).optional(),
4828
4446
  /**
4829
4447
  * Project shape. `crew` (the default) is the original behavior — the
4830
4448
  * voorman recruits and coordinates a team of specialists. `solo` is a
4831
- * "job" — a single specialist (the ambachtsman, stored in
4449
+ * "job" — a single Builder (stored in
4832
4450
  * `voormanGezelId`) handles the whole project themselves; team-
4833
4451
  * management MCP tools are stripped from their session, and the
4834
4452
  * Meester is instructed not to nominate other gezels. Missing → `crew`
4835
4453
  * for back-compat with every project on disk before this field
4836
4454
  * existed.
4837
4455
  */
4838
- mode: z21.enum(["crew", "solo"]).optional(),
4456
+ mode: z19.enum(["crew", "solo"]).optional(),
4839
4457
  /**
4840
4458
  * Optional custom label for this project's lead gezel, overriding the
4841
- * mode-based default ("Voorman" / "Ambachtsman") everywhere the UI
4459
+ * mode-based default ("Voorman" / "Builder") everywhere the UI
4842
4460
  * renders it. Set by a project type at adoption (e.g. checkers →
4843
4461
  * "Opponent"); absent → the mode default. The data field stays
4844
4462
  * `voormanGezelId` — only the label changes.
4845
4463
  */
4846
- leadLabel: z21.string().optional(),
4464
+ leadLabel: z19.string().optional(),
4847
4465
  /**
4848
4466
  * Lean-agent profile (set by a project type at adoption, e.g. checkers).
4849
4467
  * When true, sessions here get a minimal tool surface (the type's script
@@ -4851,7 +4469,7 @@ var ProjectSchema = z21.object({
4851
4469
  * scaffolding). Keeps small local models from being overwhelmed on a
4852
4470
  * focused single-purpose task. Absent → the full agent profile.
4853
4471
  */
4854
- leanProfile: z21.boolean().optional(),
4472
+ leanProfile: z19.boolean().optional(),
4855
4473
  /**
4856
4474
  * Per-project workspace-indexing switch. Missing/true preserves the
4857
4475
  * historical behavior: structural discovery plus the content-index refresh
@@ -4864,9 +4482,9 @@ var ProjectSchema = z21.object({
4864
4482
  * document index. Project-type manifests may seed the value at adoption and
4865
4483
  * the user can override it later in Project Settings.
4866
4484
  */
4867
- indexingEnabled: z21.boolean().optional(),
4485
+ indexingEnabled: z19.boolean().optional(),
4868
4486
  github: ProjectGitHubSchema.optional(),
4869
- connectors: z21.array(ProjectConnectorBindingSchema).optional(),
4487
+ connectors: z19.array(ProjectConnectorBindingSchema).optional(),
4870
4488
  nudgeConfig: ProjectNudgeConfigSchema.optional(),
4871
4489
  nudgeState: ProjectNudgeStateSchema.optional(),
4872
4490
  /**
@@ -4889,7 +4507,7 @@ var ProjectSchema = z21.object({
4889
4507
  * @deprecated Use `managedWorkspaceWritePolicy` and the centralized
4890
4508
  * `projectManagedWorkspaceWritable` resolver.
4891
4509
  */
4892
- allowGezelWrites: z21.boolean().optional(),
4510
+ allowGezelWrites: z19.boolean().optional(),
4893
4511
  /**
4894
4512
  * Per-project Codex execution posture selected from the project status bar.
4895
4513
  * It overrides per-gezel/install Codex defaults so the visible control is
@@ -4926,7 +4544,7 @@ var ProjectSchema = z21.object({
4926
4544
  * weak local model having to take an explicit action. Reversible and
4927
4545
  * non-destructive; chat and direct tool calls keep working.
4928
4546
  */
4929
- status: z21.enum(["active", "readonly", "inactive", "stable"]).optional(),
4547
+ status: z19.enum(["active", "readonly", "inactive", "stable"]).optional(),
4930
4548
  /**
4931
4549
  * Bury this project in the navigation without deleting it. Archived
4932
4550
  * projects remain available from the dedicated section in the full
@@ -4936,13 +4554,13 @@ var ProjectSchema = z21.object({
4936
4554
  * Missing/false means visible in the ordinary project UX, preserving
4937
4555
  * compatibility with projects written before archiving existed.
4938
4556
  */
4939
- archived: z21.boolean().optional(),
4557
+ archived: z19.boolean().optional(),
4940
4558
  /**
4941
4559
  * Per-project override of the `run_nodejs_script` wall-clock
4942
4560
  * timeout. Clamped between 30 seconds and 30 minutes. Missing →
4943
4561
  * the service-side default (5 min) applies.
4944
4562
  */
4945
- workspaceScriptTimeoutMs: z21.number().int().min(3e4).max(30 * 6e4).optional(),
4563
+ workspaceScriptTimeoutMs: z19.number().int().min(3e4).max(30 * 6e4).optional(),
4946
4564
  /**
4947
4565
  * Named credentials this project is explicitly allowed to use.
4948
4566
  * Credentials are stored once globally in `SecretStore`; a grant
@@ -4951,13 +4569,13 @@ var ProjectSchema = z21.object({
4951
4569
  * Missing → no credentials granted. See `scripts/dispatcher.ts`
4952
4570
  * and `secrets/registry.ts` for resolution.
4953
4571
  */
4954
- grantedCredentials: z21.array(z21.string()).optional(),
4572
+ grantedCredentials: z19.array(z19.string()).optional(),
4955
4573
  /**
4956
4574
  * Advanced exact-origin bindings for toolset credentials. Built-in provider
4957
4575
  * credentials are service-pinned and webhook credentials follow the
4958
4576
  * configured webhook URL, so entries for those names are ignored.
4959
4577
  */
4960
- credentialAllowedOrigins: z21.record(z21.string(), z21.array(HttpsOriginSchema)).optional(),
4578
+ credentialAllowedOrigins: z19.record(z19.string(), z19.array(HttpsOriginSchema)).optional(),
4961
4579
  /**
4962
4580
  * Explicit user override of the project's type (an id from the bundled
4963
4581
  * project-type taxonomy, see `project-types/taxonomy.ts`). When set, it
@@ -4965,17 +4583,17 @@ var ProjectSchema = z21.object({
4965
4583
  * (unset) → fall back to auto-detection. Missing on every project written
4966
4584
  * before this field existed.
4967
4585
  */
4968
- projectTypeId: z21.string().optional(),
4586
+ projectTypeId: z19.string().optional(),
4969
4587
  /**
4970
4588
  * Auto-detected project type, recomputed on each content-index scan from
4971
4589
  * the workspace file mix + about/mission text. Not user-editable — the
4972
4590
  * user expresses an override via `projectTypeId`. Absent until the first
4973
4591
  * scan classifies the project (or when nothing scores above the floor).
4974
4592
  */
4975
- detectedProjectType: z21.object({
4976
- id: z21.string(),
4977
- score: z21.number(),
4978
- scannedAt: z21.string()
4593
+ detectedProjectType: z19.object({
4594
+ id: z19.string(),
4595
+ score: z19.number(),
4596
+ scannedAt: z19.string()
4979
4597
  }).optional(),
4980
4598
  /**
4981
4599
  * Provenance of an applied custom project type (see docs/project-types.md).
@@ -4991,9 +4609,9 @@ var ProjectSchema = z21.object({
4991
4609
  * installer-managed shared root and operated on by this user's daemon.
4992
4610
  * The engine broker never opens the project or receives its paths.
4993
4611
  */
4994
- storageScope: z21.enum(["user", "machine-shared"]).optional(),
4995
- createdAt: z21.string(),
4996
- updatedAt: z21.string()
4612
+ storageScope: z19.enum(["user", "machine-shared"]).optional(),
4613
+ createdAt: z19.string(),
4614
+ updatedAt: z19.string()
4997
4615
  });
4998
4616
  function resolveProjectTypeId(project) {
4999
4617
  return project.projectTypeId ?? project.detectedProjectType?.id;
@@ -5002,435 +4620,898 @@ function projectAllowsAmbientWork(project) {
5002
4620
  const status = project.status ?? "active";
5003
4621
  return status === "active";
5004
4622
  }
5005
- var InstalledPackageSchema = z21.object({
5006
- name: z21.string(),
5007
- version: z21.string()
4623
+ var InstalledPackageSchema = z19.object({
4624
+ name: z19.string(),
4625
+ version: z19.string()
5008
4626
  });
5009
4627
  var ProjectDetailSchema = ProjectSchema.extend({
5010
- packages: z21.array(InstalledPackageSchema),
4628
+ packages: z19.array(InstalledPackageSchema),
5011
4629
  /** Contents of `documents/about.md` inside the project, if present. */
5012
- about: z21.string().optional(),
4630
+ about: z19.string().optional(),
5013
4631
  /** Contents of `documents/missionObjectives.md`, if present. */
5014
- missionObjectives: z21.string().optional()
4632
+ missionObjectives: z19.string().optional()
5015
4633
  });
5016
- var ProjectFileEntrySchema = z21.object({
5017
- name: z21.string(),
5018
- path: z21.string(),
5019
- isDirectory: z21.boolean(),
4634
+ var ProjectFileEntrySchema = z19.object({
4635
+ name: z19.string(),
4636
+ path: z19.string(),
4637
+ isDirectory: z19.boolean(),
5020
4638
  /** File mtime (ms epoch). Populated only when the caller opts into stats. */
5021
- mtimeMs: z21.number().optional()
4639
+ mtimeMs: z19.number().optional()
5022
4640
  });
5023
4641
  var ProjectGithubSchema = ProjectGitHubSchema;
5024
4642
 
5025
- // src/schemas/project-local.ts
5026
- import { z as z22 } from "zod";
5027
- var InstructionSourceFileSchema = z22.enum([
5028
- "AGENTS.md",
5029
- "CLAUDE.md",
5030
- ".github/copilot-instructions.md"
4643
+ // src/schemas/craftbook-test.ts
4644
+ var CRAFTBOOK_TEST_SCHEMA_VERSION = 1;
4645
+ var CRAFTBOOK_TEST_FILENAME = "test.json";
4646
+ var PrometheusAlertsCheckSchema = z20.object({
4647
+ kind: z20.literal("prometheusAlerts"),
4648
+ file: z20.string().min(1),
4649
+ minRules: z20.number().int().positive().optional(),
4650
+ maxPageAlerts: z20.number().int().nonnegative().optional(),
4651
+ allowedSeverities: z20.array(z20.string().min(1)).optional(),
4652
+ requiredServices: z20.array(z20.string().min(1)).optional(),
4653
+ requiredRunbookUrls: z20.array(z20.string().min(1)).optional()
4654
+ }).strict();
4655
+ var NodeScriptPassesCheckSchema = z20.object({
4656
+ kind: z20.literal("nodeScriptPasses"),
4657
+ script: z20.string().min(1),
4658
+ timeoutMs: z20.number().int().positive().optional(),
4659
+ requiredOutput: z20.array(
4660
+ z20.object({
4661
+ pattern: z20.string().min(1),
4662
+ flags: z20.string().optional(),
4663
+ label: z20.string().optional()
4664
+ }).strict()
4665
+ ).optional()
4666
+ }).strict();
4667
+ var BinaryDocumentCheckSchema = z20.object({
4668
+ kind: z20.literal("binaryDocument"),
4669
+ file: z20.string().min(1),
4670
+ /** Look in the artifacts drawer instead of the workspace. */
4671
+ artifact: z20.boolean().optional(),
4672
+ /** Floor on the container's byte length; defaults to 1000. */
4673
+ minBytes: z20.number().int().positive().optional()
4674
+ }).strict();
4675
+ var CraftbookTestCheckSchema = z20.union([
4676
+ GateCheckSchema,
4677
+ PrometheusAlertsCheckSchema,
4678
+ NodeScriptPassesCheckSchema,
4679
+ BinaryDocumentCheckSchema
5031
4680
  ]);
5032
- var InstructionMergeModeSchema = z22.enum(["primary", "concat"]);
5033
- var ProjectLocalConfigSchema = z22.object({
5034
- schemaVersion: z22.literal(1).default(1),
5035
- sourceFile: InstructionSourceFileSchema.optional(),
5036
- mergeMode: InstructionMergeModeSchema.optional(),
5037
- aboutHash: z22.string().optional(),
5038
- derivedAt: z22.string().optional()
5039
- });
5040
- var ImportedGezelProvenanceSchema = z22.object({
5041
- /** sha256 of the instruction file contents the gezel was last derived from. */
5042
- hash: z22.string(),
5043
- /** Which instruction file won precedence. */
5044
- sourceFile: InstructionSourceFileSchema,
5045
- mergeMode: InstructionMergeModeSchema.optional(),
5046
- /** Encoded gezel id (`proj__<projectId>__project`). */
5047
- gezelId: z22.string(),
5048
- /**
5049
- * Set true once the user edits the gezel's identity in the UI. The sync
5050
- * engine then stops touching identity (the `about` is always file-driven
5051
- * regardless).
5052
- */
5053
- userEdited: z22.boolean().optional()
5054
- });
5055
- var ImportedAboutProvenanceSchema = z22.object({
5056
- /** sha256 of the instruction content last merged into `about.md`. */
5057
- hash: z22.string(),
5058
- /** Which instruction file the merged content came from. */
5059
- sourceFile: z22.string()
5060
- });
5061
- var ImportedCraftbookProvenanceSchema = z22.object({
5062
- /** sha256 of the SKILL.md body the craftbook was last derived from. */
5063
- hash: z22.string(),
5064
- /** Project-local craftbook id this skill produced. */
5065
- craftbookId: z22.string(),
5066
- /** Generated JS script names attached to the craftbook (if any). */
5067
- scriptNames: z22.array(z22.string()).default([]),
5068
- /** True once the user edits the imported craftbook; sync stops clobbering it. */
5069
- userEdited: z22.boolean().optional()
5070
- });
5071
- var ImportProvenanceSchema = z22.object({
5072
- version: z22.literal(1).default(1),
5073
- gezel: ImportedGezelProvenanceSchema.optional(),
5074
- about: ImportedAboutProvenanceSchema.optional(),
5075
- craftbooks: z22.record(z22.string(), ImportedCraftbookProvenanceSchema).default({})
5076
- });
5077
- var PendingScriptSchema = z22.object({
5078
- /** Script file name (without `.ts`). */
5079
- name: z22.string(),
5080
- /** Translated gezel-sdk TypeScript body. */
5081
- body: z22.string(),
5082
- /** Translator self-reported confidence, 0–1. Static conversions are 1. */
5083
- confidence: z22.number().min(0).max(1),
5084
- /** The original shell snippet, for the reviewer to compare against. */
5085
- sourceBlock: z22.string(),
5086
- /** Who produced the translation. Absent on legacy items = 'llm'. */
5087
- origin: z22.enum(["static", "llm"]).optional()
5088
- });
5089
- var PendingPersonaSchema = z22.object({
5090
- role: z22.string(),
5091
- /** Generator-shaped about.md body (Identity section from the skill's intro). */
5092
- about: z22.string()
5093
- });
5094
- var PendingImportItemSchema = z22.object({
5095
- /** Workspace-relative SKILL.md path this proposal came from. */
5096
- skillSource: z22.string(),
5097
- /** sha256 of the raw skill body + companion-file list — lets sync skip an unchanged, still-pending skill. */
5098
- sourceHash: z22.string(),
5099
- /** Draft craftbook to write on approval. */
5100
- craftbook: CraftbookSchema,
5101
- /** Generated scripts to write on approval (empty when prose-only). */
5102
- scripts: z22.array(PendingScriptSchema).default([]),
5103
- /** Persona to mint on approval (persona-shaped skills only). */
5104
- persona: PendingPersonaSchema.optional(),
5105
- /** Converter honesty ledger — features of the source skill not carried over. */
5106
- notes: z22.array(z22.string()).optional(),
5107
- createdAt: z22.string()
5108
- });
5109
- var PendingImportsSchema = z22.object({
5110
- version: z22.literal(1).default(1),
5111
- items: z22.array(PendingImportItemSchema).default([])
5112
- });
5113
-
5114
- // src/schemas/task.ts
5115
- import { z as z23 } from "zod";
5116
- var TaskStatusSchema = z23.enum(["draft", "paused", "active", "complete", "canceled"]);
5117
- var TaskCronOverlapSchema = z23.enum(["skip", "queue", "concurrent"]);
5118
- var TaskCronSchema = z23.object({
5119
- expression: z23.string(),
5120
- lastTickAt: z23.string().optional(),
5121
- nextTickAt: z23.string().optional(),
5122
- overlap: TaskCronOverlapSchema.optional()
5123
- });
5124
- var TaskVariationSchema = z23.object({
5125
- title: z23.string().optional(),
5126
- plan: z23.string().optional(),
5127
- description: z23.string().optional(),
5128
- context: z23.record(z23.string(), z23.string()).optional()
5129
- });
5130
- var TaskFanoutSchema = z23.object({
5131
- count: z23.number().int().positive(),
5132
- variations: z23.array(TaskVariationSchema).optional(),
5133
- materializedAt: z23.string().optional()
5134
- });
5135
- var NewTaskFanoutSchema = z23.object({
5136
- count: z23.number().int().positive(),
5137
- variations: z23.array(TaskVariationSchema).optional()
5138
- });
5139
- var TaskNightShiftSchema = z23.object({
5140
- enabled: z23.boolean(),
5141
- onceADay: z23.boolean().optional(),
5142
- lastRunDay: z23.string().optional()
5143
- });
5144
- var TaskCraftbookStepSchema = CraftbookStepSchema.extend({
5145
- createdAt: z23.string(),
5146
- completedAt: z23.string().optional(),
5147
- attemptCount: z23.number().int().nonnegative().optional(),
5148
- lastActivatedAt: z23.string().optional(),
5149
- /**
5150
- * Completion-gate rejections since this step last activated. Distinct
5151
- * from `attemptCount` (which counts ACTIVATIONS — loop-backs): a
5152
- * rejection holds the step active without re-activating it. Reset by
5153
- * `bumpStepActivation` so a loop-back grants a fresh budget.
5154
- */
5155
- gateAttempts: z23.number().int().nonnegative().optional(),
4681
+ var MockServiceIdSchema = z20.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "mock service ids are lowercase kebab-case");
4682
+ var MockToolsetIdSchema = z20.string().min(1).regex(
4683
+ /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/,
4684
+ "mock toolset ids are lowercase catalog ids or scoped npm-style ids"
4685
+ );
4686
+ var MockHttpRouteSchema = z20.object({
4687
+ method: z20.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET"),
4688
+ /** Exact path, `:param` segments, or a trailing `*` wildcard. */
4689
+ path: z20.string().min(1),
4690
+ status: z20.number().int().min(100).max(599).default(200),
4691
+ headers: z20.record(z20.string(), z20.string()).optional(),
4692
+ /** String bodies are served verbatim; anything else is JSON-encoded. */
4693
+ body: z20.unknown(),
4694
+ latencyMs: z20.number().int().nonnegative().optional()
4695
+ }).strict();
4696
+ var MockServiceSchema = z20.discriminatedUnion("kind", [
4697
+ z20.object({
4698
+ kind: z20.literal("http"),
4699
+ id: MockServiceIdSchema,
4700
+ description: z20.string().min(1),
4701
+ /**
4702
+ * v1 mock HTTP is reachable ONLY through the `http.authed`
4703
+ * credential rail (anonymous script/browser HTTP hard-rejects
4704
+ * loopback), so a credential is required. The harness seeds it and
4705
+ * grants the mock's exact origin on the trial project.
4706
+ */
4707
+ credential: z20.object({
4708
+ name: z20.string().regex(/^mock\.[a-z0-9][a-z0-9.-]*$/, "mock credentials are named mock.<service-id>"),
4709
+ authScheme: z20.enum(["bearer", "basic"]).optional()
4710
+ }).strict(),
4711
+ routes: z20.array(MockHttpRouteSchema).min(1)
4712
+ }).strict(),
4713
+ z20.object({
4714
+ kind: z20.literal("webhook"),
4715
+ id: MockServiceIdSchema,
4716
+ description: z20.string().min(1),
4717
+ /** Receiver path; defaults to `/webhook` when omitted. */
4718
+ path: z20.string().min(1).optional()
4719
+ }).strict(),
4720
+ z20.object({
4721
+ kind: z20.literal("cli"),
4722
+ id: MockServiceIdSchema,
4723
+ description: z20.string().min(1),
4724
+ /** Fake-CLI shim seeded as a workspace file (today's dry-run pattern). */
4725
+ shim: z20.object({ path: z20.string().min(1), content: z20.string() }).strict()
4726
+ }).strict(),
4727
+ z20.object({
4728
+ kind: z20.literal("mcp"),
4729
+ id: MockServiceIdSchema,
4730
+ description: z20.string().min(1),
4731
+ /** Override the local-catalog id when the mock replaces a real dependency. */
4732
+ toolsetId: MockToolsetIdSchema.optional(),
4733
+ /**
4734
+ * Served live by the eval mock rail: each declared tool becomes a
4735
+ * real tool on a per-trial Streamable-HTTP MCP endpoint, installed
4736
+ * into the trial via a local-catalog `mock-mcp-<id>` toolset.
4737
+ * `resultTemplate` is JSON-encoded as the tool result text
4738
+ * (default `{"ok":true}` when absent).
4739
+ */
4740
+ tools: z20.array(
4741
+ z20.object({
4742
+ name: z20.string().min(1),
4743
+ description: z20.string().min(1),
4744
+ resultTemplate: z20.unknown().optional(),
4745
+ /** Deterministic stateful responses, consumed in call order; the last repeats. */
4746
+ resultSequence: z20.array(z20.unknown()).min(1).optional(),
4747
+ /**
4748
+ * Deterministic eval-only file materialization after this
4749
+ * tool call. The fixture must match the container the
4750
+ * deliverable's extension claims a `minimal-pptx` written
4751
+ * to a `.docx` path now fails the `binaryDocument` check on
4752
+ * content type rather than sliding through a byte floor.
4753
+ */
4754
+ writeFixture: z20.object({
4755
+ surface: z20.enum(["workspace", "artifact"]),
4756
+ pathArgument: z20.string().min(1),
4757
+ fixture: z20.enum(["minimal-pptx", "minimal-docx", "minimal-pdf", "minimal-png"])
4758
+ }).strict().optional()
4759
+ }).strict()
4760
+ ).min(1)
4761
+ }).strict()
4762
+ ]);
4763
+ var CraftbookTestFixtureFileSchema = z20.object({
4764
+ path: z20.string().min(1),
4765
+ content: z20.string(),
4766
+ /** Defaults to `workspace`; `harness` never enters the model-visible project. */
4767
+ surface: z20.enum(["workspace", "artifact", "harness"]).optional(),
5156
4768
  /**
5157
- * Repeat-reject damper. When the gated deliverable is byte-identical
5158
- * to what the gate last rejected, the runtime returns this cached
5159
- * rejection instead of re-running gate scripts, and the chat nudge
5160
- * dedupes on `messageFingerprint`.
4769
+ * Whether the fixture is presented to the model as source material.
4770
+ * Defaults to true; false still seeds the file for browsers and graders.
5161
4771
  */
5162
- lastGateReject: z23.object({
5163
- contentHash: z23.string().optional(),
5164
- messageFingerprint: z23.string(),
5165
- message: z23.string(),
5166
- at: z23.string()
5167
- }).optional(),
4772
+ modelInput: z20.boolean().optional()
4773
+ }).strict();
4774
+ var CraftbookTestWorkerSchema = z20.object({
4775
+ name: z20.string().min(1),
4776
+ role: z20.string().min(1),
4777
+ description: z20.string().optional(),
4778
+ about: z20.string().optional()
4779
+ }).strict();
4780
+ var CraftbookTestSetupSchema = z20.object({
4781
+ projectName: z20.string().min(1),
4782
+ about: z20.string().optional(),
4783
+ missionObjectives: z20.string().optional(),
4784
+ /** Reproduce project write posture before the craftbook task starts. */
4785
+ managedWorkspaceWritePolicy: ProjectManagedWorkspaceWritePolicySchema.optional(),
4786
+ files: z20.array(CraftbookTestFixtureFileSchema).default([]),
4787
+ /** Exact values supplied to the catalog craftbook's `paramSchema`. */
4788
+ craftbookParams: z20.record(z20.string(), z20.string()).optional(),
5168
4789
  /**
5169
- * Anti-stall re-drive bookkeeping for the idle step supervisor
5170
- * (`TaskScheduler.sweepStuckSteps`). `redriveCount` = how many times the
5171
- * supervisor has re-poked THIS active step after it went idle without
5172
- * advancing; `lastRedriveAt` = when it last did (also the cooldown
5173
- * anchor so a re-drive isn't itself read as fresh progress). Distinct
5174
- * from `attemptCount` (activations) and `gateAttempts` (gate
5175
- * rejections), which count model-driven events — these count autonomous
5176
- * re-pokes of a silent assignee. Reset by `bumpStepActivation` so a
5177
- * fresh activation / loop-back grants a fresh re-drive budget.
4790
+ * Direct execution target. When present the harness seeds this gezel
4791
+ * and sends the kickoff straight to it (measuring whether the book
4792
+ * guides the work); absent the Meester routes.
5178
4793
  */
5179
- redriveCount: z23.number().int().nonnegative().optional(),
5180
- lastRedriveAt: z23.string().optional(),
4794
+ worker: CraftbookTestWorkerSchema.optional()
4795
+ }).strict();
4796
+ var CraftbookTestDeliverableSchema = z20.object({
4797
+ path: z20.string().min(1),
4798
+ kind: DeliverableKindSchema,
4799
+ /** Grade the path in the project's artifacts drawer, not its workspace. */
4800
+ artifact: z20.boolean().optional(),
4801
+ minBytes: z20.number().int().positive().optional(),
4802
+ checks: z20.array(CraftbookTestCheckSchema).optional()
4803
+ }).strict();
4804
+ var CraftbookTestMockExpectationSchema = z20.object({
4805
+ /** Mock service id from `mocks[]`. */
4806
+ service: MockServiceIdSchema,
4807
+ minRequests: z20.number().int().positive().optional(),
4808
+ /** Regex sources matched against logged request paths. */
4809
+ requiredPaths: z20.array(z20.string().min(1)).optional(),
4810
+ forbiddenPaths: z20.array(z20.string().min(1)).optional(),
5181
4811
  /**
5182
- * Rolling reject trail (capped at 8 entries, oldest dropped). One entry
5183
- * per real completion-gate rejection PLUS one per damped byte-identical
5184
- * resubmit (`frozen: true`). `signatureHash` hashes the failing-check
5185
- * IDENTITY set (GateCheckOutcome labels), not prose or bytes — byte
5186
- * churn with an unmoved failure set IS a plateau; a cleared check
5187
- * changes the signature and resets the ladder.
5188
- *
5189
- * Deliberately NOT stripped by `bumpStepActivation`: `onReject: <self>`
5190
- * loop gates reset `gateAttempts`/`lastGateReject` on every pass, so
5191
- * this trail is the only cross-activation plateau memory. Self-healing
5192
- * — real progress changes the trailing signature.
4812
+ * Exact MCP tool names that must each have been called at least once
4813
+ * on this service (`kind: 'mcp'` only). Exact names, not regexes —
4814
+ * the tool roster is fully declared in the same file, so a pattern
4815
+ * buys nothing and invites drift. Cross-checked against the mock's
4816
+ * declared `tools[]` at parse time.
5193
4817
  */
5194
- gateAttemptHistory: z23.array(
5195
- z23.object({
5196
- at: z23.string(),
5197
- attempt: z23.number().int().nonnegative(),
5198
- contentHash: z23.string().optional(),
5199
- signatureHash: z23.string(),
5200
- messageFingerprint: z23.string(),
5201
- /** Failing GateCheckOutcome labels (or `script:<name>`). */
5202
- failedChecks: z23.array(z23.string()).optional(),
5203
- /** True when this entry records a damped byte-identical resubmit. */
5204
- frozen: z23.boolean().optional()
5205
- })
4818
+ requiredTools: z20.array(z20.string().min(1)).optional(),
4819
+ /** Per-MCP-tool call budgets for repeated journeys or retries. */
4820
+ toolCalls: z20.record(
4821
+ z20.string().min(1),
4822
+ z20.object({
4823
+ minCalls: z20.number().int().nonnegative().default(1),
4824
+ maxCalls: z20.number().int().nonnegative().optional()
4825
+ }).strict().refine(
4826
+ (value) => value.maxCalls === void 0 || value.minCalls <= value.maxCalls,
4827
+ "minCalls must be less than or equal to maxCalls"
4828
+ )
5206
4829
  ).optional()
5207
- });
5208
- var TaskCraftbookSchema = z23.object({
5209
- id: z23.string().min(1),
5210
- name: z23.string().min(1),
5211
- description: z23.string().optional(),
5212
- version: z23.string().optional(),
5213
- basedOn: CraftbookBasedOnSchema.optional(),
5214
- plan: z23.string().optional(),
5215
- defaultAssignee: TaskAssigneeSchema.optional(),
5216
- steps: z23.array(TaskCraftbookStepSchema).min(1),
5217
- entryStepId: z23.string().min(1),
5218
- triggers: z23.array(z23.string()).optional(),
5219
- hooks: z23.array(HookSpecSchema).optional(),
5220
- /** Invocation schema retained with the task snapshot for audit/UI context. */
5221
- paramSchema: z23.record(z23.string(), z23.unknown()).optional(),
5222
- toolsets: z23.array(CraftbookToolsetNeedSchema).optional(),
5223
- connectors: z23.array(CraftbookConnectorNeedSchema).optional(),
5224
- /**
5225
- * Embedded script sources snapshotted from the source craftbook, so the
5226
- * task's gate/lifecycle scripts execute from its own copy — `scope:
5227
- * 'craftbook'` refs resolve here first, project-installed copy second.
5228
- */
5229
- scripts: CraftbookScriptsSchema.optional(),
4830
+ }).strict();
4831
+ var CraftbookTestHistoryExpectationSchema = z20.object({
4832
+ kind: HistoryEventKindSchema,
4833
+ minEntries: z20.number().int().nonnegative().default(1),
4834
+ maxEntries: z20.number().int().nonnegative().optional(),
4835
+ summaryPattern: z20.string().min(1).optional(),
4836
+ flags: z20.string().optional(),
4837
+ details: z20.record(z20.string(), z20.union([z20.string(), z20.number(), z20.boolean(), z20.null()])).optional()
4838
+ }).strict();
4839
+ var CraftbookTestSuccessSchema = z20.object({
4840
+ summary: z20.string().min(1),
4841
+ deliverables: z20.array(CraftbookTestDeliverableSchema).optional(),
4842
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4843
+ taskNotes: z20.object({
4844
+ minBytes: z20.number().int().positive().optional(),
4845
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4846
+ requireCraftbookTask: z20.boolean().optional()
4847
+ }).strict().optional(),
4848
+ taskGraph: z20.object({
4849
+ checks: z20.array(CraftbookTestCheckSchema).optional(),
4850
+ requireCraftbookTask: z20.boolean().optional(),
4851
+ /** Require the matching task to reach a terminal step (or complete). */
4852
+ requireTerminalStep: z20.boolean().optional(),
4853
+ requireDraftRef: z20.boolean().optional(),
4854
+ draft: z20.object({
4855
+ status: z20.enum(["draft", "paused", "active", "complete", "canceled"]).optional(),
4856
+ minDescriptionBytes: z20.number().int().positive().optional(),
4857
+ minOutcomes: z20.number().int().positive().optional(),
4858
+ minSteps: z20.number().int().positive().optional(),
4859
+ requireTerminalVerification: z20.boolean().optional(),
4860
+ requireGatedBuildSteps: z20.boolean().optional()
4861
+ }).strict().optional()
4862
+ }).strict().optional(),
4863
+ /** Assertions evaluated against the live mock server's request log. */
4864
+ mocks: z20.array(CraftbookTestMockExpectationSchema).optional(),
4865
+ /** Assertions evaluated against the project's append-only History log. */
4866
+ history: z20.array(CraftbookTestHistoryExpectationSchema).optional(),
4867
+ /** Workspace fixtures whose final content must equal the seeded bytes exactly. */
4868
+ unchangedFixtures: z20.array(z20.string().min(1)).optional()
4869
+ }).strict();
4870
+ var CraftbookTestRubricSchema = z20.object({
4871
+ artifact: z20.object({
4872
+ /** Workspace or artifact path the judge reads (adapter derives the basename). */
4873
+ path: z20.string().min(1),
4874
+ kind: z20.enum(["html", "markdown", "yaml", "typescript", "json", "text"])
4875
+ }).strict(),
4876
+ axes: z20.array(z20.object({ name: z20.string().min(1), description: z20.string().min(1) }).strict()).min(1),
4877
+ contextNote: z20.string().optional()
4878
+ }).strict();
4879
+ var CraftbookTestSpecSchema = z20.object({
4880
+ schemaVersion: z20.literal(CRAFTBOOK_TEST_SCHEMA_VERSION),
4881
+ title: z20.string().min(1),
4882
+ objective: z20.string().min(1),
5230
4883
  /**
5231
- * Declarative per-item fanout config, snapshotted from the source
5232
- * craftbook so the runtime reads it at fanout time (the `spawnFanout`
5233
- * step's activation reads `spawn.overFile` from the workspace). See
5234
- * {@link CraftbookSpawnSchema}.
4884
+ * Task-class taxonomy tags (e.g. `html-game`, `corpus`, `external`).
4885
+ * The single declared source for harness selection and batch
4886
+ * planning replaces the old regex classifiers.
5235
4887
  */
5236
- spawn: CraftbookSpawnSchema.optional(),
5237
- createdAt: z23.string(),
5238
- updatedAt: z23.string(),
4888
+ tags: z20.array(z20.string().min(1)).default([]),
4889
+ /** Kickoff chat message the harness sends. Required — every book runs. */
4890
+ prompt: z20.string().min(1),
4891
+ setup: CraftbookTestSetupSchema,
4892
+ mocks: z20.array(MockServiceSchema).default([]),
4893
+ success: CraftbookTestSuccessSchema,
4894
+ rubric: CraftbookTestRubricSchema,
4895
+ qualityFocus: z20.array(z20.string().min(1)).default([]),
5239
4896
  /**
5240
- * Tier the embedded book was collapse-rendered for (D3). Stamped by
5241
- * the tier-collapse pass at handoff dispatch; presence means "already
5242
- * rendered — do not re-collapse". One-way in v1: gates are carried
5243
- * verbatim, so a later re-route to a bigger model just walks fewer
5244
- * steps.
4897
+ * Sanctioned escape hatch for experiments carried opaquely, never
4898
+ * interpreted by CI. Promote a field out of here before relying on it.
5245
4899
  */
5246
- renderedForTier: ModelTierSchema.optional()
5247
- });
5248
- var TaskCraftbookSourceSchema = z23.object({
5249
- role: z23.enum(["main", "spawn"]),
5250
- catalogId: z23.string(),
5251
- version: z23.string().optional(),
5252
- /** Source identifier from the catalog (e.g. "bundled", "local"). */
5253
- sourceId: z23.string().optional()
4900
+ extensions: z20.record(z20.string(), z20.unknown()).optional()
4901
+ }).strict().superRefine((spec, ctx) => {
4902
+ const mockIds = new Set(spec.mocks.map((m) => m.id));
4903
+ const mockById = new Map(spec.mocks.map((m) => [m.id, m]));
4904
+ for (const [i, expectation] of (spec.success.mocks ?? []).entries()) {
4905
+ if (!mockIds.has(expectation.service)) {
4906
+ ctx.addIssue({
4907
+ code: z20.ZodIssueCode.custom,
4908
+ path: ["success", "mocks", i, "service"],
4909
+ message: `success.mocks[${i}] references unknown mock service "${expectation.service}"`
4910
+ });
4911
+ }
4912
+ if (expectation.requiredTools && expectation.requiredTools.length > 0) {
4913
+ const target = mockById.get(expectation.service);
4914
+ if (target && target.kind !== "mcp") {
4915
+ ctx.addIssue({
4916
+ code: z20.ZodIssueCode.custom,
4917
+ path: ["success", "mocks", i, "requiredTools"],
4918
+ message: `success.mocks[${i}].requiredTools requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4919
+ });
4920
+ } else if (target?.kind === "mcp") {
4921
+ const declared = new Set(target.tools.map((tool) => tool.name));
4922
+ for (const name of expectation.requiredTools) {
4923
+ if (!declared.has(name)) {
4924
+ ctx.addIssue({
4925
+ code: z20.ZodIssueCode.custom,
4926
+ path: ["success", "mocks", i, "requiredTools"],
4927
+ message: `success.mocks[${i}].requiredTools names undeclared tool "${name}" on mcp service "${expectation.service}"`
4928
+ });
4929
+ }
4930
+ }
4931
+ }
4932
+ }
4933
+ if (expectation.toolCalls && Object.keys(expectation.toolCalls).length > 0) {
4934
+ const target = mockById.get(expectation.service);
4935
+ if (target && target.kind !== "mcp") {
4936
+ ctx.addIssue({
4937
+ code: z20.ZodIssueCode.custom,
4938
+ path: ["success", "mocks", i, "toolCalls"],
4939
+ message: `success.mocks[${i}].toolCalls requires an mcp service; "${expectation.service}" is kind "${target.kind}"`
4940
+ });
4941
+ } else if (target?.kind === "mcp") {
4942
+ const declared = new Set(target.tools.map((tool) => tool.name));
4943
+ for (const name of Object.keys(expectation.toolCalls)) {
4944
+ if (!declared.has(name)) {
4945
+ ctx.addIssue({
4946
+ code: z20.ZodIssueCode.custom,
4947
+ path: ["success", "mocks", i, "toolCalls", name],
4948
+ message: `success.mocks[${i}].toolCalls names undeclared tool "${name}" on mcp service "${expectation.service}"`
4949
+ });
4950
+ }
4951
+ }
4952
+ }
4953
+ }
4954
+ }
4955
+ for (const [i, mock] of spec.mocks.entries()) {
4956
+ if (mock.kind === "http" && mock.credential.name !== `mock.${mock.id}`) {
4957
+ ctx.addIssue({
4958
+ code: z20.ZodIssueCode.custom,
4959
+ path: ["mocks", i, "credential", "name"],
4960
+ message: `http mock "${mock.id}" must use credential name "mock.${mock.id}"`
4961
+ });
4962
+ }
4963
+ }
4964
+ const workspaceFixtures = new Set(
4965
+ spec.setup.files.filter((file) => file.surface === void 0 || file.surface === "workspace").map((file) => file.path)
4966
+ );
4967
+ for (const [i, path] of (spec.success.unchangedFixtures ?? []).entries()) {
4968
+ if (!workspaceFixtures.has(path)) {
4969
+ ctx.addIssue({
4970
+ code: z20.ZodIssueCode.custom,
4971
+ path: ["success", "unchangedFixtures", i],
4972
+ message: `unchanged fixture "${path}" is not a seeded workspace file`
4973
+ });
4974
+ }
4975
+ }
4976
+ for (const [i, expectation] of (spec.success.history ?? []).entries()) {
4977
+ if (expectation.maxEntries !== void 0 && expectation.minEntries > expectation.maxEntries) {
4978
+ ctx.addIssue({
4979
+ code: z20.ZodIssueCode.custom,
4980
+ path: ["success", "history", i],
4981
+ message: "minEntries must be less than or equal to maxEntries"
4982
+ });
4983
+ }
4984
+ }
5254
4985
  });
5255
- var OutcomeSchema = z23.object({
5256
- id: z23.string(),
5257
- text: z23.string().min(1),
5258
- met: z23.boolean().optional(),
5259
- evidence: z23.string().optional(),
5260
- verifiedAt: z23.string().optional()
4986
+ function parseCraftbookTestSpec(raw, opts) {
4987
+ const mode = opts?.mode ?? "strict";
4988
+ const candidate = mode === "tolerant" ? deepStripUnknown(raw) : raw;
4989
+ const parsed = CraftbookTestSpecSchema.safeParse(candidate);
4990
+ if (parsed.success) return { ok: true, spec: parsed.data };
4991
+ return {
4992
+ ok: false,
4993
+ errors: parsed.error.issues.map((issue) => {
4994
+ const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "<root>";
4995
+ return `${path}: ${issue.message}`;
4996
+ })
4997
+ };
4998
+ }
4999
+ function deepStripUnknown(raw) {
5000
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
5001
+ const record = structuredClone(raw);
5002
+ const version = record.schemaVersion;
5003
+ if (typeof version === "number" && Number.isInteger(version) && version > CRAFTBOOK_TEST_SCHEMA_VERSION) {
5004
+ record.schemaVersion = CRAFTBOOK_TEST_SCHEMA_VERSION;
5005
+ }
5006
+ for (let pass = 0; pass < 16; pass++) {
5007
+ const attempt = CraftbookTestSpecSchema.safeParse(record);
5008
+ if (attempt.success) return record;
5009
+ const unknownKeyIssues = attempt.error.issues.filter(
5010
+ (issue) => issue.code === z20.ZodIssueCode.unrecognized_keys
5011
+ );
5012
+ if (unknownKeyIssues.length === 0) return record;
5013
+ for (const issue of unknownKeyIssues) {
5014
+ const target = resolvePath(record, issue.path);
5015
+ if (target && typeof target === "object" && !Array.isArray(target)) {
5016
+ for (const key of issue.keys) delete target[key];
5017
+ }
5018
+ }
5019
+ }
5020
+ return record;
5021
+ }
5022
+ function resolvePath(root, path) {
5023
+ let node = root;
5024
+ for (const segment of path) {
5025
+ if (node === null || typeof node !== "object") return void 0;
5026
+ node = node[segment];
5027
+ }
5028
+ return node;
5029
+ }
5030
+
5031
+ // src/schemas/codex-setup.ts
5032
+ import { z as z21 } from "zod";
5033
+ var CodexSetupModelOptionSchema = z21.object({
5034
+ id: z21.string().min(1),
5035
+ label: z21.string().min(1),
5036
+ description: z21.string().optional(),
5037
+ kind: z21.enum(["gezel", "model"]).default("model"),
5038
+ provider: z21.string().min(1),
5039
+ /** Stable gezel id for persona-backed entries. Absent on raw-model entries. */
5040
+ gezelId: z21.string().min(1).optional(),
5041
+ role: z21.string().min(1).optional(),
5042
+ /** Human-readable name of the effective inference model behind a gezel. */
5043
+ modelLabel: z21.string().min(1).optional(),
5044
+ contextWindow: z21.number().int().positive().optional(),
5045
+ supportsReasoning: z21.boolean().optional(),
5046
+ supportsTools: z21.boolean().optional()
5261
5047
  });
5262
- var TaskSchema = z23.object({
5263
- projectId: z23.string(),
5264
- num: z23.number().int().positive(),
5265
- ref: z23.string(),
5266
- title: z23.string(),
5267
- description: z23.string().optional(),
5268
- plan: z23.string().optional(),
5269
- /**
5270
- * Expected outcomes — prose of what should be created/updated at
5271
- * successful completion, each individually verifiable. Authored during
5272
- * planning; checked by the terminal verification step. See `OutcomeSchema`.
5273
- */
5274
- outcomes: z23.array(OutcomeSchema).optional(),
5275
- status: TaskStatusSchema,
5276
- assignee: TaskAssigneeSchema,
5277
- /**
5278
- * The assignee was derived, not chosen — it mirrors whoever the ENTRY
5279
- * step's `suggestedRole` resolved to. Set when a task is created
5280
- * without a named assignee; cleared the moment anyone pins one.
5281
- *
5282
- * A draft carries the flag with an interim `{kind:'user'}` assignee:
5283
- * drafts resolve no roles (that would create gezels for a task that
5284
- * may never run), so `activate()` is the first moment a concrete
5285
- * specialist exists to point at.
5286
- */
5287
- assigneeAuto: z23.boolean().optional(),
5288
- craftbook: TaskCraftbookSchema,
5289
- spawnsCraftbook: TaskCraftbookSchema.optional(),
5290
- sourceCraftbookIds: z23.array(TaskCraftbookSourceSchema).optional(),
5291
- /**
5292
- * Invocation-time parameter values supplied when this task was
5293
- * launched from a parameterized craftbook (the command launcher).
5294
- * Stringified for the CLI round-trip; the declared types live on the
5295
- * craftbook's `paramSchema`. Surfaced directly in every task-scoped
5296
- * prompt so later specialists retain the authoritative inputs even when
5297
- * an invocation path did not also stamp an entry-step note.
5298
- */
5299
- craftbookParams: z23.record(z23.string(), z23.string()).optional(),
5300
- /**
5301
- * Invocation parameters for a schedule/fanout host's child template.
5302
- * Copied to each spawned child's `craftbookParams`.
5303
- */
5304
- spawnsCraftbookParams: z23.record(z23.string(), z23.string()).optional(),
5305
- activeStepId: z23.string().optional(),
5306
- parentTaskRef: z23.string().optional(),
5048
+ var CodexSetupStateSchema = z21.enum([
5049
+ "not-configured",
5050
+ "configured",
5051
+ "update-needed",
5052
+ "conflict",
5053
+ "unavailable"
5054
+ ]);
5055
+ var CodexSetupStatusResponseSchema = z21.object({
5056
+ state: CodexSetupStateSchema,
5057
+ models: z21.array(CodexSetupModelOptionSchema),
5058
+ configuredModel: z21.string().optional(),
5059
+ recommendedModel: z21.string().optional(),
5060
+ reasons: z21.array(z21.string()),
5061
+ message: z21.string().optional(),
5062
+ codexInstalled: z21.boolean(),
5063
+ codexVersion: z21.string().optional(),
5064
+ codexPath: z21.string().optional(),
5065
+ endpointsEnabled: z21.boolean(),
5066
+ profileName: z21.string().min(1),
5067
+ profilePath: z21.string().min(1),
5068
+ launchCommand: z21.string().min(1),
5069
+ bridge: z21.object({
5070
+ baseUrl: z21.string().url(),
5071
+ listening: z21.boolean(),
5072
+ port: z21.number().int().nonnegative()
5073
+ }),
5074
+ canConfigure: z21.boolean(),
5075
+ /** Whether Gezel-owned credential/state material exists and can be safely removed. */
5076
+ canRemove: z21.boolean()
5077
+ });
5078
+ var ConfigureCodexRequestSchema = z21.object({
5079
+ model: z21.string().min(1)
5080
+ });
5081
+
5082
+ // src/schemas/project-local.ts
5083
+ import { z as z22 } from "zod";
5084
+ var InstructionSourceFileSchema = z22.enum([
5085
+ "AGENTS.md",
5086
+ "CLAUDE.md",
5087
+ ".github/copilot-instructions.md"
5088
+ ]);
5089
+ var InstructionMergeModeSchema = z22.enum(["primary", "concat"]);
5090
+ var ProjectLocalConfigSchema = z22.object({
5091
+ schemaVersion: z22.literal(1).default(1),
5092
+ sourceFile: InstructionSourceFileSchema.optional(),
5093
+ mergeMode: InstructionMergeModeSchema.optional(),
5094
+ aboutHash: z22.string().optional(),
5095
+ derivedAt: z22.string().optional()
5096
+ });
5097
+ var ImportedGezelProvenanceSchema = z22.object({
5098
+ /** sha256 of the instruction file contents the gezel was last derived from. */
5099
+ hash: z22.string(),
5100
+ /** Which instruction file won precedence. */
5101
+ sourceFile: InstructionSourceFileSchema,
5102
+ mergeMode: InstructionMergeModeSchema.optional(),
5103
+ /** Encoded gezel id (`proj__<projectId>__project`). */
5104
+ gezelId: z22.string(),
5307
5105
  /**
5308
- * Provenance for service-materialized tasks (today: project-type
5309
- * schedule hosts). The dedup key that makes re-applying a type
5310
- * idempotent — apply scans for a matching origin instead of creating a
5311
- * second host. Deliberately absent from `CreateTaskRequestSchema`:
5312
- * models and HTTP callers can't stamp it; only the service does, via
5313
- * `TaskManager.create` extras.
5106
+ * Set true once the user edits the gezel's identity in the UI. The sync
5107
+ * engine then stops touching identity (the `about` is always file-driven
5108
+ * regardless).
5314
5109
  */
5315
- origin: z23.discriminatedUnion("kind", [
5316
- z23.object({
5317
- kind: z23.literal("project-type-schedule"),
5318
- typeId: z23.string(),
5319
- /** Schedule identity within the type — craftbook id, `#N`-suffixed on repeats. */
5320
- scheduleKey: z23.string()
5321
- }),
5322
- z23.object({
5323
- /**
5324
- * A durable control surface for work executed by the service
5325
- * itself. `managedByGezelId` is presentation/provenance only:
5326
- * the user assignee remains in place so TaskRunner never opens
5327
- * a duplicate model session for the system loop.
5328
- */
5329
- kind: z23.literal("system-job"),
5330
- jobId: z23.string(),
5331
- managedByGezelId: z23.string().optional()
5332
- }),
5333
- z23.object({
5334
- /** A fix task linked back to one durable project-local BW issue. */
5335
- kind: z23.literal("boekwachter-issue"),
5336
- issueRef: z23.string().regex(/^BW-[1-9]\d*$/),
5337
- path: z23.string().min(1)
5338
- }),
5339
- z23.object({
5340
- /**
5341
- * A host materialized from a gezel template's `suggestedCraftbooks`
5342
- * entry via the suggested-work layer. `suggestionKey` is the
5343
- * entry's key within the template (`<craftbookId>` or
5344
- * `<craftbookId>#N` on repeats) together with `templateId` it is
5345
- * the toggle identity: enable resurrects a matching paused host
5346
- * instead of creating a second one.
5347
- */
5348
- kind: z23.literal("gezel-suggested-craftbook"),
5349
- templateId: z23.string(),
5350
- suggestionKey: z23.string()
5351
- })
5352
- ]).optional(),
5353
- cron: TaskCronSchema.optional(),
5354
- nightShift: TaskNightShiftSchema.optional(),
5355
- fanout: TaskFanoutSchema.optional(),
5110
+ userEdited: z22.boolean().optional()
5111
+ });
5112
+ var ImportedAboutProvenanceSchema = z22.object({
5113
+ /** sha256 of the instruction content last merged into `about.md`. */
5114
+ hash: z22.string(),
5115
+ /** Which instruction file the merged content came from. */
5116
+ sourceFile: z22.string()
5117
+ });
5118
+ var ImportedCraftbookProvenanceSchema = z22.object({
5119
+ /** sha256 of the SKILL.md body the craftbook was last derived from. */
5120
+ hash: z22.string(),
5121
+ /** Project-local craftbook id this skill produced. */
5122
+ craftbookId: z22.string(),
5123
+ /** Generated JS script names attached to the craftbook (if any). */
5124
+ scriptNames: z22.array(z22.string()).default([]),
5125
+ /** True once the user edits the imported craftbook; sync stops clobbering it. */
5126
+ userEdited: z22.boolean().optional()
5127
+ });
5128
+ var ImportProvenanceSchema = z22.object({
5129
+ version: z22.literal(1).default(1),
5130
+ gezel: ImportedGezelProvenanceSchema.optional(),
5131
+ about: ImportedAboutProvenanceSchema.optional(),
5132
+ craftbooks: z22.record(z22.string(), ImportedCraftbookProvenanceSchema).default({})
5133
+ });
5134
+ var PendingScriptSchema = z22.object({
5135
+ /** Script file name (without `.ts`). */
5136
+ name: z22.string(),
5137
+ /** Translated gezel-sdk TypeScript body. */
5138
+ body: z22.string(),
5139
+ /** Translator self-reported confidence, 0–1. Static conversions are 1. */
5140
+ confidence: z22.number().min(0).max(1),
5141
+ /** The original shell snippet, for the reviewer to compare against. */
5142
+ sourceBlock: z22.string(),
5143
+ /** Who produced the translation. Absent on legacy items = 'llm'. */
5144
+ origin: z22.enum(["static", "llm"]).optional()
5145
+ });
5146
+ var PendingPersonaSchema = z22.object({
5147
+ role: z22.string(),
5148
+ /** Generator-shaped about.md body (Identity section from the skill's intro). */
5149
+ about: z22.string()
5150
+ });
5151
+ var PendingImportItemSchema = z22.object({
5152
+ /** Workspace-relative SKILL.md path this proposal came from. */
5153
+ skillSource: z22.string(),
5154
+ /** sha256 of the raw skill body + companion-file list — lets sync skip an unchanged, still-pending skill. */
5155
+ sourceHash: z22.string(),
5156
+ /** Draft craftbook to write on approval. */
5157
+ craftbook: CraftbookSchema,
5158
+ /** Generated scripts to write on approval (empty when prose-only). */
5159
+ scripts: z22.array(PendingScriptSchema).default([]),
5160
+ /** Persona to mint on approval (persona-shaped skills only). */
5161
+ persona: PendingPersonaSchema.optional(),
5162
+ /** Converter honesty ledger — features of the source skill not carried over. */
5163
+ notes: z22.array(z22.string()).optional(),
5164
+ createdAt: z22.string()
5165
+ });
5166
+ var PendingImportsSchema = z22.object({
5167
+ version: z22.literal(1).default(1),
5168
+ items: z22.array(PendingImportItemSchema).default([])
5169
+ });
5170
+
5171
+ // src/schemas/task.ts
5172
+ import { z as z23 } from "zod";
5173
+ var TaskStatusSchema = z23.enum(["draft", "paused", "active", "complete", "canceled"]);
5174
+ var TaskCronOverlapSchema = z23.enum(["skip", "queue", "concurrent"]);
5175
+ var TaskCronSchema = z23.object({
5176
+ expression: z23.string(),
5177
+ lastTickAt: z23.string().optional(),
5178
+ nextTickAt: z23.string().optional(),
5179
+ overlap: TaskCronOverlapSchema.optional()
5180
+ });
5181
+ var TaskVariationSchema = z23.object({
5182
+ title: z23.string().optional(),
5183
+ plan: z23.string().optional(),
5184
+ description: z23.string().optional(),
5185
+ context: z23.record(z23.string(), z23.string()).optional()
5186
+ });
5187
+ var TaskFanoutSchema = z23.object({
5188
+ count: z23.number().int().positive(),
5189
+ variations: z23.array(TaskVariationSchema).optional(),
5190
+ materializedAt: z23.string().optional()
5191
+ });
5192
+ var NewTaskFanoutSchema = z23.object({
5193
+ count: z23.number().int().positive(),
5194
+ variations: z23.array(TaskVariationSchema).optional()
5195
+ });
5196
+ var TaskNightShiftSchema = z23.object({
5197
+ enabled: z23.boolean(),
5198
+ onceADay: z23.boolean().optional(),
5199
+ lastRunDay: z23.string().optional()
5200
+ });
5201
+ var TaskCraftbookStepSchema = CraftbookStepSchema.extend({
5202
+ createdAt: z23.string(),
5203
+ completedAt: z23.string().optional(),
5204
+ attemptCount: z23.number().int().nonnegative().optional(),
5205
+ lastActivatedAt: z23.string().optional(),
5356
5206
  /**
5357
- * Handoff payload stamped by the most recent approving gate script.
5358
- * Injected verbatim into the next step's handoff seed prompt (and
5359
- * readable by scripts via `tasks.read`); replaced on each approval.
5207
+ * Completion-gate rejections since this step last activated. Distinct
5208
+ * from `attemptCount` (which counts ACTIVATIONS loop-backs): a
5209
+ * rejection holds the step active without re-activating it. Reset by
5210
+ * `bumpStepActivation` so a loop-back grants a fresh budget.
5360
5211
  */
5361
- lastGateHandoff: z23.object({
5362
- fromStepId: z23.string(),
5363
- toStepId: z23.string().optional(),
5212
+ gateAttempts: z23.number().int().nonnegative().optional(),
5213
+ /**
5214
+ * Repeat-reject damper. When the gated deliverable is byte-identical
5215
+ * to what the gate last rejected, the runtime returns this cached
5216
+ * rejection instead of re-running gate scripts, and the chat nudge
5217
+ * dedupes on `messageFingerprint`.
5218
+ */
5219
+ lastGateReject: z23.object({
5220
+ contentHash: z23.string().optional(),
5221
+ messageFingerprint: z23.string(),
5364
5222
  message: z23.string(),
5365
- params: z23.record(z23.string(), z23.unknown()).optional(),
5366
5223
  at: z23.string()
5367
5224
  }).optional(),
5368
- createdAt: z23.string(),
5369
- updatedAt: z23.string(),
5370
- createdBy: TaskAssigneeSchema
5225
+ /**
5226
+ * Anti-stall re-drive bookkeeping for the idle step supervisor
5227
+ * (`TaskScheduler.sweepStuckSteps`). `redriveCount` = how many times the
5228
+ * supervisor has re-poked THIS active step after it went idle without
5229
+ * advancing; `lastRedriveAt` = when it last did (also the cooldown
5230
+ * anchor so a re-drive isn't itself read as fresh progress). Distinct
5231
+ * from `attemptCount` (activations) and `gateAttempts` (gate
5232
+ * rejections), which count model-driven events — these count autonomous
5233
+ * re-pokes of a silent assignee. Reset by `bumpStepActivation` so a
5234
+ * fresh activation / loop-back grants a fresh re-drive budget.
5235
+ */
5236
+ redriveCount: z23.number().int().nonnegative().optional(),
5237
+ lastRedriveAt: z23.string().optional(),
5238
+ /**
5239
+ * Rolling reject trail (capped at 8 entries, oldest dropped). One entry
5240
+ * per real completion-gate rejection PLUS one per damped byte-identical
5241
+ * resubmit (`frozen: true`). `signatureHash` hashes the failing-check
5242
+ * IDENTITY set (GateCheckOutcome labels), not prose or bytes — byte
5243
+ * churn with an unmoved failure set IS a plateau; a cleared check
5244
+ * changes the signature and resets the ladder.
5245
+ *
5246
+ * Deliberately NOT stripped by `bumpStepActivation`: `onReject: <self>`
5247
+ * loop gates reset `gateAttempts`/`lastGateReject` on every pass, so
5248
+ * this trail is the only cross-activation plateau memory. Self-healing
5249
+ * — real progress changes the trailing signature.
5250
+ */
5251
+ gateAttemptHistory: z23.array(
5252
+ z23.object({
5253
+ at: z23.string(),
5254
+ attempt: z23.number().int().nonnegative(),
5255
+ contentHash: z23.string().optional(),
5256
+ signatureHash: z23.string(),
5257
+ messageFingerprint: z23.string(),
5258
+ /** Failing GateCheckOutcome labels (or `script:<name>`). */
5259
+ failedChecks: z23.array(z23.string()).optional(),
5260
+ /** True when this entry records a damped byte-identical resubmit. */
5261
+ frozen: z23.boolean().optional()
5262
+ })
5263
+ ).optional()
5371
5264
  });
5372
- var CreateTaskRequestSchema = z23.object({
5373
- title: z23.string().min(1),
5374
- description: z23.string().min(40),
5265
+ var TaskCraftbookSchema = z23.object({
5266
+ id: z23.string().min(1),
5267
+ name: z23.string().min(1),
5268
+ description: z23.string().optional(),
5269
+ version: z23.string().optional(),
5270
+ basedOn: CraftbookBasedOnSchema.optional(),
5375
5271
  plan: z23.string().optional(),
5376
- outcomes: z23.array(OutcomeSchema).optional(),
5272
+ defaultAssignee: TaskAssigneeSchema.optional(),
5273
+ steps: z23.array(TaskCraftbookStepSchema).min(1),
5274
+ entryStepId: z23.string().min(1),
5275
+ triggers: z23.array(z23.string()).optional(),
5276
+ hooks: z23.array(HookSpecSchema).optional(),
5277
+ /** Invocation schema retained with the task snapshot for audit/UI context. */
5278
+ paramSchema: z23.record(z23.string(), z23.unknown()).optional(),
5279
+ toolsets: z23.array(CraftbookToolsetNeedSchema).optional(),
5280
+ connectors: z23.array(CraftbookConnectorNeedSchema).optional(),
5377
5281
  /**
5378
- * Who owns the task. Omit it on a craftbook whose entry step names a
5379
- * `suggestedRole` and the resolved specialist becomes the assignee
5380
- * naming one here would only be an arbitrary pick that the role
5381
- * resolution overrides at step level anyway. Falls back to the user
5382
- * when nothing resolves. See `TaskSchema.assigneeAuto`.
5282
+ * Embedded script sources snapshotted from the source craftbook, so the
5283
+ * task's gate/lifecycle scripts execute from its own copy `scope:
5284
+ * 'craftbook'` refs resolve here first, project-installed copy second.
5383
5285
  */
5384
- assignee: TaskAssigneeSchema.optional(),
5286
+ scripts: CraftbookScriptsSchema.optional(),
5385
5287
  /**
5386
- * Initial status. Defaults to 'active'. Pass 'draft' to create an
5387
- * inert task (e.g. a plan being authored) that won't tick or dispatch
5388
- * until `activate`d. Drafts may not be schedule hosts.
5288
+ * Declarative per-item fanout config, snapshotted from the source
5289
+ * craftbook so the runtime reads it at fanout time (the `spawnFanout`
5290
+ * step's activation reads `spawn.overFile` from the workspace). See
5291
+ * {@link CraftbookSpawnSchema}.
5389
5292
  */
5390
- status: z23.enum(["draft", "active"]).optional(),
5391
- /** Resolve the main craftbook from the catalog by id. */
5392
- craftbookId: z23.string().optional(),
5393
- /** Catalog source id, when distinguishing local from bundled etc. */
5394
- craftbookSourceId: z23.string().optional(),
5395
- /** Specific catalog version of the main craftbook. */
5396
- craftbookVersion: z23.string().optional(),
5397
- /** Inline blueprint for the main craftbook (mutually exclusive with craftbookId). */
5398
- steps: z23.array(NewCraftbookStepSchema).optional(),
5399
- /** Optional entry step id when supplying inline steps; defaults to first step. */
5400
- entryStepId: z23.string().optional(),
5401
- /** Invocation-time param values for the main craftbook (launcher). */
5402
- craftbookParams: z23.record(z23.string(), z23.string()).optional(),
5403
- /** Invocation-time param values copied to each spawned child. */
5404
- spawnsCraftbookParams: z23.record(z23.string(), z23.string()).optional(),
5405
- /** Spawn-side (for schedule hosts and fanouts): catalog reference. */
5406
- spawnsCraftbookId: z23.string().optional(),
5407
- spawnsCraftbookSourceId: z23.string().optional(),
5408
- spawnsCraftbookVersion: z23.string().optional(),
5409
- spawnsSteps: z23.array(NewCraftbookStepSchema).optional(),
5410
- spawnsEntryStepId: z23.string().optional(),
5411
- parentTaskRef: z23.string().optional(),
5412
- cron: z23.object({
5413
- expression: z23.string(),
5414
- overlap: TaskCronOverlapSchema.optional()
5415
- }).optional(),
5416
- nightShift: z23.object({
5417
- enabled: z23.boolean(),
5418
- onceADay: z23.boolean().optional()
5419
- }).optional(),
5420
- fanout: NewTaskFanoutSchema.optional(),
5421
- createdBy: TaskAssigneeSchema.optional(),
5293
+ spawn: CraftbookSpawnSchema.optional(),
5294
+ createdAt: z23.string(),
5295
+ updatedAt: z23.string(),
5422
5296
  /**
5423
- * Enqueue the entry-step handoff immediately after create the
5424
- * single-channel kickoff (there is no "tell a gezel about work"
5425
- * separate from "hand a gezel the work"). The worker starts in a
5426
- * task-scoped session with the step prompt + gate contract
5427
- * in-prompt. Invalid on drafts (they kick off via `activate`) and
5428
- * on cron/fanout hosts (their children dispatch via their own
5429
- * activation hooks — flag-dispatching the host would double-engage).
5297
+ * Tier the embedded book was collapse-rendered for (D3). Stamped by
5298
+ * the tier-collapse pass at handoff dispatch; presence means "already
5299
+ * rendered do not re-collapse". One-way in v1: gates are carried
5300
+ * verbatim, so a later re-route to a bigger model just walks fewer
5301
+ * steps.
5430
5302
  */
5431
- dispatchEntry: z23.boolean().optional()
5432
- }).refine((v) => !!v.craftbookId !== !!(v.steps && v.steps.length > 0), {
5433
- message: "exactly one of craftbookId or steps must be provided for the main craftbook",
5303
+ renderedForTier: ModelTierSchema.optional()
5304
+ });
5305
+ var TaskCraftbookSourceSchema = z23.object({
5306
+ role: z23.enum(["main", "spawn"]),
5307
+ catalogId: z23.string(),
5308
+ version: z23.string().optional(),
5309
+ /** Source identifier from the catalog (e.g. "bundled", "local"). */
5310
+ sourceId: z23.string().optional()
5311
+ });
5312
+ var OutcomeSchema = z23.object({
5313
+ id: z23.string(),
5314
+ text: z23.string().min(1),
5315
+ met: z23.boolean().optional(),
5316
+ evidence: z23.string().optional(),
5317
+ verifiedAt: z23.string().optional()
5318
+ });
5319
+ var TaskSchema = z23.object({
5320
+ projectId: z23.string(),
5321
+ num: z23.number().int().positive(),
5322
+ ref: z23.string(),
5323
+ title: z23.string(),
5324
+ description: z23.string().optional(),
5325
+ plan: z23.string().optional(),
5326
+ /**
5327
+ * Expected outcomes — prose of what should be created/updated at
5328
+ * successful completion, each individually verifiable. Authored during
5329
+ * planning; checked by the terminal verification step. See `OutcomeSchema`.
5330
+ */
5331
+ outcomes: z23.array(OutcomeSchema).optional(),
5332
+ status: TaskStatusSchema,
5333
+ assignee: TaskAssigneeSchema,
5334
+ /**
5335
+ * The assignee was derived, not chosen — it mirrors whoever the ENTRY
5336
+ * step's `suggestedRole` resolved to. Set when a task is created
5337
+ * without a named assignee; cleared the moment anyone pins one.
5338
+ *
5339
+ * A draft carries the flag with an interim `{kind:'user'}` assignee:
5340
+ * drafts resolve no roles (that would create gezels for a task that
5341
+ * may never run), so `activate()` is the first moment a concrete
5342
+ * specialist exists to point at.
5343
+ */
5344
+ assigneeAuto: z23.boolean().optional(),
5345
+ craftbook: TaskCraftbookSchema,
5346
+ spawnsCraftbook: TaskCraftbookSchema.optional(),
5347
+ sourceCraftbookIds: z23.array(TaskCraftbookSourceSchema).optional(),
5348
+ /**
5349
+ * Invocation-time parameter values supplied when this task was
5350
+ * launched from a parameterized craftbook (the command launcher).
5351
+ * Stringified for the CLI round-trip; the declared types live on the
5352
+ * craftbook's `paramSchema`. Surfaced directly in every task-scoped
5353
+ * prompt so later specialists retain the authoritative inputs even when
5354
+ * an invocation path did not also stamp an entry-step note.
5355
+ */
5356
+ craftbookParams: z23.record(z23.string(), z23.string()).optional(),
5357
+ /**
5358
+ * Invocation parameters for a schedule/fanout host's child template.
5359
+ * Copied to each spawned child's `craftbookParams`.
5360
+ */
5361
+ spawnsCraftbookParams: z23.record(z23.string(), z23.string()).optional(),
5362
+ activeStepId: z23.string().optional(),
5363
+ parentTaskRef: z23.string().optional(),
5364
+ /**
5365
+ * Provenance for service-materialized tasks (today: project-type
5366
+ * schedule hosts). The dedup key that makes re-applying a type
5367
+ * idempotent — apply scans for a matching origin instead of creating a
5368
+ * second host. Deliberately absent from `CreateTaskRequestSchema`:
5369
+ * models and HTTP callers can't stamp it; only the service does, via
5370
+ * `TaskManager.create` extras.
5371
+ */
5372
+ origin: z23.discriminatedUnion("kind", [
5373
+ z23.object({
5374
+ kind: z23.literal("project-type-schedule"),
5375
+ typeId: z23.string(),
5376
+ /** Schedule identity within the type — craftbook id, `#N`-suffixed on repeats. */
5377
+ scheduleKey: z23.string()
5378
+ }),
5379
+ z23.object({
5380
+ /**
5381
+ * A durable control surface for work executed by the service
5382
+ * itself. `managedByGezelId` is presentation/provenance only:
5383
+ * the user assignee remains in place so TaskRunner never opens
5384
+ * a duplicate model session for the system loop.
5385
+ */
5386
+ kind: z23.literal("system-job"),
5387
+ jobId: z23.string(),
5388
+ managedByGezelId: z23.string().optional()
5389
+ }),
5390
+ z23.object({
5391
+ /** A fix task linked back to one durable project-local BW issue. */
5392
+ kind: z23.literal("boekwachter-issue"),
5393
+ issueRef: z23.string().regex(/^BW-[1-9]\d*$/),
5394
+ path: z23.string().min(1)
5395
+ }),
5396
+ z23.object({
5397
+ /**
5398
+ * An invoke_craftbook call keyed to one persisted root chat turn.
5399
+ * The HTTP task boundary uses this opaque digest to return the first
5400
+ * still-active task when a provider continuation repeats the call.
5401
+ */
5402
+ kind: z23.literal("craftbook-invocation"),
5403
+ key: z23.string().regex(/^craftbook-root-v1:[a-f0-9]{64}$/)
5404
+ }),
5405
+ z23.object({
5406
+ /**
5407
+ * A host materialized from a gezel template's `suggestedCraftbooks`
5408
+ * entry via the suggested-work layer. `suggestionKey` is the
5409
+ * entry's key within the template (`<craftbookId>` or
5410
+ * `<craftbookId>#N` on repeats) — together with `templateId` it is
5411
+ * the toggle identity: enable resurrects a matching paused host
5412
+ * instead of creating a second one.
5413
+ */
5414
+ kind: z23.literal("gezel-suggested-craftbook"),
5415
+ templateId: z23.string(),
5416
+ suggestionKey: z23.string()
5417
+ })
5418
+ ]).optional(),
5419
+ cron: TaskCronSchema.optional(),
5420
+ nightShift: TaskNightShiftSchema.optional(),
5421
+ fanout: TaskFanoutSchema.optional(),
5422
+ /**
5423
+ * Handoff payload stamped by the most recent approving gate script.
5424
+ * Injected verbatim into the next step's handoff seed prompt (and
5425
+ * readable by scripts via `tasks.read`); replaced on each approval.
5426
+ */
5427
+ lastGateHandoff: z23.object({
5428
+ fromStepId: z23.string(),
5429
+ toStepId: z23.string().optional(),
5430
+ message: z23.string(),
5431
+ params: z23.record(z23.string(), z23.unknown()).optional(),
5432
+ at: z23.string()
5433
+ }).optional(),
5434
+ /**
5435
+ * Naming presentation inherited from the session that launched this
5436
+ * workflow. Task handoffs create fresh sessions (and may be rehydrated
5437
+ * after restart), so they cannot rely on the launcher's session record
5438
+ * still being available when the next step starts.
5439
+ */
5440
+ roleBasedNameOnlyMode: z23.boolean().optional(),
5441
+ createdAt: z23.string(),
5442
+ updatedAt: z23.string(),
5443
+ createdBy: TaskAssigneeSchema
5444
+ });
5445
+ var CreateTaskRequestSchema = z23.object({
5446
+ title: z23.string().min(1),
5447
+ description: z23.string().min(40),
5448
+ plan: z23.string().optional(),
5449
+ outcomes: z23.array(OutcomeSchema).optional(),
5450
+ /**
5451
+ * Who owns the task. Omit it on a craftbook whose entry step names a
5452
+ * `suggestedRole` and the resolved specialist becomes the assignee —
5453
+ * naming one here would only be an arbitrary pick that the role
5454
+ * resolution overrides at step level anyway. Falls back to the user
5455
+ * when nothing resolves. See `TaskSchema.assigneeAuto`.
5456
+ */
5457
+ assignee: TaskAssigneeSchema.optional(),
5458
+ /**
5459
+ * Initial status. Defaults to 'active'. Pass 'draft' to create an
5460
+ * inert task (e.g. a plan being authored) that won't tick or dispatch
5461
+ * until `activate`d. Drafts may not be schedule hosts.
5462
+ */
5463
+ status: z23.enum(["draft", "active"]).optional(),
5464
+ /** Resolve the main craftbook from the catalog by id. */
5465
+ craftbookId: z23.string().optional(),
5466
+ /** Catalog source id, when distinguishing local from bundled etc. */
5467
+ craftbookSourceId: z23.string().optional(),
5468
+ /** Specific catalog version of the main craftbook. */
5469
+ craftbookVersion: z23.string().optional(),
5470
+ /** Inline blueprint for the main craftbook (mutually exclusive with craftbookId). */
5471
+ steps: z23.array(NewCraftbookStepSchema).optional(),
5472
+ /** Optional entry step id when supplying inline steps; defaults to first step. */
5473
+ entryStepId: z23.string().optional(),
5474
+ /** Invocation-time param values for the main craftbook (launcher). */
5475
+ craftbookParams: z23.record(z23.string(), z23.string()).optional(),
5476
+ /** Invocation-time param values copied to each spawned child. */
5477
+ spawnsCraftbookParams: z23.record(z23.string(), z23.string()).optional(),
5478
+ /** Spawn-side (for schedule hosts and fanouts): catalog reference. */
5479
+ spawnsCraftbookId: z23.string().optional(),
5480
+ spawnsCraftbookSourceId: z23.string().optional(),
5481
+ spawnsCraftbookVersion: z23.string().optional(),
5482
+ spawnsSteps: z23.array(NewCraftbookStepSchema).optional(),
5483
+ spawnsEntryStepId: z23.string().optional(),
5484
+ parentTaskRef: z23.string().optional(),
5485
+ cron: z23.object({
5486
+ expression: z23.string(),
5487
+ overlap: TaskCronOverlapSchema.optional()
5488
+ }).optional(),
5489
+ nightShift: z23.object({
5490
+ enabled: z23.boolean(),
5491
+ onceADay: z23.boolean().optional()
5492
+ }).optional(),
5493
+ fanout: NewTaskFanoutSchema.optional(),
5494
+ createdBy: TaskAssigneeSchema.optional(),
5495
+ /** Preserve the launcher's naming presentation across task handoffs. */
5496
+ roleBasedNameOnlyMode: z23.boolean().optional(),
5497
+ /**
5498
+ * Enqueue the entry-step handoff immediately after create — the
5499
+ * single-channel kickoff (there is no "tell a gezel about work"
5500
+ * separate from "hand a gezel the work"). The worker starts in a
5501
+ * task-scoped session with the step prompt + gate contract
5502
+ * in-prompt. Invalid on drafts (they kick off via `activate`) and
5503
+ * on cron/fanout hosts (their children dispatch via their own
5504
+ * activation hooks — flag-dispatching the host would double-engage).
5505
+ */
5506
+ dispatchEntry: z23.boolean().optional(),
5507
+ /**
5508
+ * Internal invoke_craftbook idempotency digest. The task route converts
5509
+ * it into service-owned Task.origin provenance; ordinary create_task
5510
+ * callers omit it.
5511
+ */
5512
+ craftbookInvocationKey: z23.string().regex(/^craftbook-root-v1:[a-f0-9]{64}$/).optional()
5513
+ }).refine((v) => !!v.craftbookId !== !!(v.steps && v.steps.length > 0), {
5514
+ message: "exactly one of craftbookId or steps must be provided for the main craftbook",
5434
5515
  path: ["craftbookId"]
5435
5516
  }).refine(
5436
5517
  (v) => {
@@ -5532,6 +5613,8 @@ var UpdateTaskStepRequestSchema = z23.object({
5532
5613
  /** Step automation hooks (single ref or ordered list). `null` detaches. */
5533
5614
  onEnter: ScriptRefListSchema.nullable().optional(),
5534
5615
  onExit: ScriptRefListSchema.nullable().optional(),
5616
+ /** Required file inputs for the step. `null` clears the declaration. */
5617
+ consumes: z23.array(CraftbookStepInputSchema).min(1).nullable().optional(),
5535
5618
  /** Auto-advance contract. `null` clears it. */
5536
5619
  advanceWhen: AdvanceWhenSchema.nullable().optional(),
5537
5620
  /** The end-of-step gate (current or legacy shape). `null` clears it. */
@@ -6342,7 +6425,7 @@ var ProjectTypeCompositionShape = {
6342
6425
  tabVisibility: ProjectTabVisibilitySchema.optional(),
6343
6426
  /**
6344
6427
  * Project shape for instances of this type. `solo` marks a single-gezel
6345
- * "ambachtsman" experience (games, the chat room): the one roster gezel
6428
+ * "Builder" experience (games, the chat room): the one roster gezel
6346
6429
  * does everything, no separate overseer voorman is recruited, and the UI
6347
6430
  * collapses the crew picker to that lead. Maps onto the project's `mode`
6348
6431
  * at adoption. Omit for the default crew shape.
@@ -6350,9 +6433,9 @@ var ProjectTypeCompositionShape = {
6350
6433
  mode: z26.enum(["crew", "solo"]).optional(),
6351
6434
  /**
6352
6435
  * Custom label for this type's project lead, shown wherever the generic
6353
- * "Voorman" / "Ambachtsman" would appear (the chat chip, project
6436
+ * "Voorman" / "Builder" would appear (the chat chip, project
6354
6437
  * settings) — e.g. checkers uses "Opponent". Aimed at solo types that
6355
- * want a domain word instead of "Ambachtsman". The underlying data field
6438
+ * want a domain word instead of "Builder". The underlying data field
6356
6439
  * stays `voormanGezelId`; only the rendered label changes.
6357
6440
  */
6358
6441
  leadLabel: z26.string().min(1).optional(),
@@ -6461,6 +6544,13 @@ var ConnectorActionSchema = z26.object({
6461
6544
  /** Consent gate this action clears at commit time (e.g. `recipient-allowlist`). */
6462
6545
  consentScope: z26.string()
6463
6546
  });
6547
+ var ConnectorSetupInstructionsSchema = z26.object({
6548
+ title: z26.string().min(1),
6549
+ description: z26.string().min(1).optional(),
6550
+ steps: z26.array(z26.string().min(1)).optional(),
6551
+ url: z26.string().url().optional(),
6552
+ urlLabel: z26.string().min(1).optional()
6553
+ });
6464
6554
  var ConnectorTypeCompositionShape = {
6465
6555
  /** Which driver executes the fetch. */
6466
6556
  driver: ConnectorDriverSchema,
@@ -6468,6 +6558,8 @@ var ConnectorTypeCompositionShape = {
6468
6558
  configSchema: z26.record(z26.string(), z26.unknown()).optional(),
6469
6559
  /** Shape of the credential the binding stores in the SecretStore. */
6470
6560
  secretShape: z26.record(z26.string(), z26.unknown()).optional(),
6561
+ /** Concise setup guidance rendered above the binding form. */
6562
+ setupInstructions: ConnectorSetupInstructionsSchema.optional(),
6471
6563
  /**
6472
6564
  * Driver-specific fetch config: `{adapterId}` (native) | `{server,list,fetch}`
6473
6565
  * (mcp) | `{fetch|cli}` (script) | `{component,action,...}` (spectral).
@@ -6777,6 +6869,15 @@ var ChatModelMlxSourceSchema = z26.object({
6777
6869
  });
6778
6870
  var ChatModelIdentitySchema = IdentityCommonSchema.extend({
6779
6871
  kind: z26.literal("chat-model"),
6872
+ /**
6873
+ * Organization that created the core model weights when it differs from
6874
+ * the catalog maintainer (for example, a quant maintained by its converter).
6875
+ * Omit when `maintainer` already names the maker.
6876
+ */
6877
+ maker: z26.object({
6878
+ name: z26.string().min(1),
6879
+ url: z26.string().url().optional()
6880
+ }).optional(),
6780
6881
  parameterSize: z26.string(),
6781
6882
  supportsTools: z26.boolean(),
6782
6883
  contextWindow: z26.number().int().positive().optional(),
@@ -6874,6 +6975,11 @@ var ChatModelManifestSchema = z26.object({
6874
6975
  name: z26.string(),
6875
6976
  url: z26.string().url().optional()
6876
6977
  }),
6978
+ /** Core-model maker; present when it differs from `maintainer`. */
6979
+ maker: z26.object({
6980
+ name: z26.string().min(1),
6981
+ url: z26.string().url().optional()
6982
+ }).optional(),
6877
6983
  logo: z26.string().optional(),
6878
6984
  license: z26.string().optional(),
6879
6985
  ...LicenseMetaShape,
@@ -7685,6 +7791,18 @@ var ChatSessionSchema = z28.object({
7685
7791
  }),
7686
7792
  /** Snapshot of the gezel's about.md at session creation, for drift warnings. */
7687
7793
  aboutSnapshot: z28.string().optional(),
7794
+ /**
7795
+ * Per-session override of `config.roleBasedNameOnlyMode` ("boring mode").
7796
+ * Stamped at creation when the creating client has a fixed presentation
7797
+ * mode — the TUI always renders role-based labels, so it pins the
7798
+ * sessions it creates to `true`. This keeps the prompt (what the model
7799
+ * is told about other gezels) consistent with what that client shows;
7800
+ * without it the TUI displayed `reviewer:` while the system prompt said
7801
+ * "the voorman is Tomas", and the model naturally leaked names into
7802
+ * prose. Unset = follow the live config flag, which is what desktop
7803
+ * sessions do.
7804
+ */
7805
+ roleBasedNameOnlyMode: z28.boolean().optional(),
7688
7806
  /** Set when the last attempted resume failed — UI surfaces a banner. */
7689
7807
  resumeFailed: z28.boolean().optional(),
7690
7808
  /**
@@ -7913,7 +8031,14 @@ var CreateChatSessionRequestSchema = z28.object({
7913
8031
  projectId: z28.string().optional(),
7914
8032
  taskRef: z28.string().optional(),
7915
8033
  stepId: z28.string().optional(),
7916
- craftbookRef: z28.string().optional()
8034
+ craftbookRef: z28.string().optional(),
8035
+ /**
8036
+ * Pin the session's name-rendering mode instead of following
8037
+ * `config.roleBasedNameOnlyMode`. Passed by clients whose presentation
8038
+ * mode is fixed (the TUI) so prompt-side name rendering matches their
8039
+ * labels. See `ChatSessionSchema.roleBasedNameOnlyMode`.
8040
+ */
8041
+ roleBasedNameOnlyMode: z28.boolean().optional()
7917
8042
  });
7918
8043
  var ListChatSessionsResponseSchema = z28.object({
7919
8044
  sessions: z28.array(ChatSessionSummarySchema)
@@ -8539,13 +8664,26 @@ var GitHubPullFileSchema = z33.object({
8539
8664
  additions: z33.number().int(),
8540
8665
  deletions: z33.number().int(),
8541
8666
  changes: z33.number().int(),
8542
- /** Truncated unified diff hunk, if returned by GitHub. */
8667
+ /** Unified diff hunk, if requested and returned by GitHub. */
8543
8668
  patch: z33.string().optional(),
8669
+ /** Character count before Gezel's local patch budget was applied. */
8670
+ patchChars: z33.number().int().nonnegative().optional(),
8671
+ /** True when Gezel clipped `patch`; callers must request the file/diff directly. */
8672
+ patchTruncated: z33.boolean().optional(),
8544
8673
  /** Prior path when `status === 'renamed'`. */
8545
8674
  previousFilename: z33.string().optional()
8546
8675
  });
8547
8676
  var ListGitHubPullFilesResponseSchema = z33.object({
8548
- files: z33.array(GitHubPullFileSchema)
8677
+ files: z33.array(GitHubPullFileSchema),
8678
+ /** Total files in the PR before an optional path filter. */
8679
+ allFiles: z33.number().int().nonnegative().optional(),
8680
+ /** Total files selected by the optional path filter. */
8681
+ totalFiles: z33.number().int().nonnegative().optional(),
8682
+ offset: z33.number().int().nonnegative().optional(),
8683
+ limit: z33.number().int().positive().optional(),
8684
+ hasMore: z33.boolean().optional(),
8685
+ nextOffset: z33.number().int().nonnegative().optional(),
8686
+ includesPatch: z33.boolean().optional()
8549
8687
  });
8550
8688
  var GitHubPullCommentSchema = z33.object({
8551
8689
  id: z33.number(),
@@ -8562,7 +8700,14 @@ var ListGitHubPullCommentsResponseSchema = z33.object({
8562
8700
  });
8563
8701
  var GitHubPullDiffResponseSchema = z33.object({
8564
8702
  number: z33.number().int(),
8565
- diff: z33.string()
8703
+ diff: z33.string(),
8704
+ /** Exact changed path when this is a file-scoped diff. */
8705
+ path: z33.string().optional(),
8706
+ offset: z33.number().int().nonnegative().optional(),
8707
+ returnedChars: z33.number().int().nonnegative().optional(),
8708
+ totalChars: z33.number().int().nonnegative().optional(),
8709
+ truncated: z33.boolean().optional(),
8710
+ nextOffset: z33.number().int().nonnegative().optional()
8566
8711
  });
8567
8712
  var GitHubCreateCommentRequestSchema = z33.object({
8568
8713
  body: z33.string().min(1)
@@ -10850,6 +10995,13 @@ var GezelConfigSchema = z37.object({
10850
10995
  * Advanced.
10851
10996
  */
10852
10997
  showAdvancedFeatures: z37.boolean().optional(),
10998
+ /**
10999
+ * When `true`, very early work-in-progress surfaces are revealed in the
11000
+ * UI and CLI. This is a discoverability preference, not a security or
11001
+ * service-layer capability boundary. Defaults on in development builds
11002
+ * and off in releases; an explicit user choice always wins.
11003
+ */
11004
+ showWorkInProgressFeatures: z37.boolean().optional(),
10853
11005
  /**
10854
11006
  * Debug-only opt-in: when `true`, the service rewrites every
10855
11007
  * template-derived gezel's `about.md` back to the prose its catalog
@@ -11126,7 +11278,7 @@ var GezelConfigSchema = z37.object({
11126
11278
  * crew with granular per-step craftbooks. Right for raw-completion
11127
11279
  * local models, where gezel's loop IS the agent.
11128
11280
  * - `flat`: route concrete asks to a single generalist (`start_job` →
11129
- * solo "ambachtsman", which already collapses the craftbook onto the
11281
+ * solo Builder, which already collapses the craftbook onto the
11130
11282
  * one specialist). Right for self-orchestrating providers (codex-cli,
11131
11283
  * anthropic-cli, copilot) that bring their own agent loop — the crew
11132
11284
  * + granular steps are mostly redundant overhead for them (eval data:
@@ -12030,6 +12182,12 @@ var ListProjectsResponseSchema = z37.object({
12030
12182
  var CreateProjectRequestSchema = z37.object({
12031
12183
  name: z37.string().min(1),
12032
12184
  description: z37.string().optional(),
12185
+ /**
12186
+ * Existing local folder to use as the project workspace. Folder-backed
12187
+ * projects are created with ambient Meester progress check-ins disabled;
12188
+ * the user can opt back in from Project Settings.
12189
+ */
12190
+ workingDir: z37.string().min(1).optional(),
12033
12191
  // about/missionObjectives are *encouraged, not required* at the wire level.
12034
12192
  // The New Project dialog still enforces the 60/40 richness minimums for the
12035
12193
  // blank/GitHub flows (where the user is authoring context from scratch), but
@@ -12042,7 +12200,7 @@ var CreateProjectRequestSchema = z37.object({
12042
12200
  missionObjectives: z37.string().optional().describe('Concrete success criteria \u2014 usually a bullet list. What does "done" look like?'),
12043
12201
  /**
12044
12202
  * Project shape. `crew` (default) → traditional voorman-coordinates-
12045
- * specialists. `solo` → a "job": one specialist (the ambachtsman) does
12203
+ * specialists. `solo` → a "job": one Builder does
12046
12204
  * everything themselves; team-management tools are filtered out for
12047
12205
  * sessions on this project.
12048
12206
  */
@@ -17460,6 +17618,7 @@ var STEP_FENCE_KEYS = [
17460
17618
  "deliverable",
17461
17619
  "onEnter",
17462
17620
  "onExit",
17621
+ "consumes",
17463
17622
  "advanceWhen",
17464
17623
  "gate",
17465
17624
  "next",
@@ -18757,6 +18916,68 @@ function isMoEFromTags(tags) {
18757
18916
  });
18758
18917
  }
18759
18918
 
18919
+ // src/model-attribution.ts
18920
+ function modelAttribution(manifest) {
18921
+ const sourceOwners = [
18922
+ manifest.llamaCpp?.huggingfaceRepo,
18923
+ manifest.mlx?.huggingfaceRepo,
18924
+ manifest.ds4?.huggingfaceRepo
18925
+ ].map((repo) => repo ? huggingFaceOwnerFromRepo(repo) : null).filter((owner) => owner !== null);
18926
+ const explicitMakerOwner = manifest.maker?.url ? huggingFaceOwnerFromUrl(manifest.maker.url) : null;
18927
+ const maintainerOwner = manifest.maintainer.url ? huggingFaceOwnerFromUrl(manifest.maintainer.url) : null;
18928
+ const upstreamOwner = manifest.upstream ? huggingFaceOwnerFromUrl(manifest.upstream) : null;
18929
+ const normalizedSources = new Set(sourceOwners.map(normalizeOrganization));
18930
+ const maintainerIsSource = [manifest.maintainer.name, maintainerOwner].filter((name) => name !== null).some((name) => normalizedSources.has(normalizeOrganization(name)));
18931
+ const upstreamIsSource = upstreamOwner ? normalizedSources.has(normalizeOrganization(upstreamOwner)) : false;
18932
+ const makerFromUpstream = !manifest.maker && maintainerIsSource && upstreamOwner && !upstreamIsSource;
18933
+ const maker = manifest.maker?.name ?? (makerFromUpstream ? displayOrganizationName(upstreamOwner) : manifest.maintainer.name);
18934
+ const makerAliases = /* @__PURE__ */ new Set([normalizeOrganization(maker)]);
18935
+ if (explicitMakerOwner) makerAliases.add(normalizeOrganization(explicitMakerOwner));
18936
+ if (upstreamOwner) makerAliases.add(normalizeOrganization(upstreamOwner));
18937
+ if (!manifest.maker && !makerFromUpstream) {
18938
+ makerAliases.add(normalizeOrganization(manifest.maintainer.name));
18939
+ if (maintainerOwner) makerAliases.add(normalizeOrganization(maintainerOwner));
18940
+ }
18941
+ const customizers = [];
18942
+ const seen = /* @__PURE__ */ new Set();
18943
+ const customizerCandidates = manifest.maker ? [manifest.maintainer.name, ...sourceOwners] : sourceOwners;
18944
+ for (const owner of customizerCandidates) {
18945
+ const normalized = normalizeOrganization(owner);
18946
+ if (!normalized || makerAliases.has(normalized) || seen.has(normalized)) continue;
18947
+ seen.add(normalized);
18948
+ customizers.push(owner);
18949
+ }
18950
+ return { maker, customizers };
18951
+ }
18952
+ function displayOrganizationName(owner) {
18953
+ const knownNames = {
18954
+ "deepseek-ai": "DeepSeek",
18955
+ "zai-org": "Z.ai"
18956
+ };
18957
+ return knownNames[owner.toLowerCase()] ?? owner;
18958
+ }
18959
+ function formatModelAttribution(manifest) {
18960
+ const attribution = modelAttribution(manifest);
18961
+ return attribution.customizers.length > 0 ? `${attribution.maker}, customized by ${attribution.customizers.join(", ")}` : attribution.maker;
18962
+ }
18963
+ function huggingFaceOwnerFromRepo(repo) {
18964
+ const slash = repo.indexOf("/");
18965
+ return slash > 0 ? repo.slice(0, slash) : null;
18966
+ }
18967
+ function huggingFaceOwnerFromUrl(value) {
18968
+ try {
18969
+ const url = new URL(value);
18970
+ if (url.hostname.toLowerCase() !== "huggingface.co") return null;
18971
+ const owner = url.pathname.split("/").filter(Boolean)[0];
18972
+ return owner ?? null;
18973
+ } catch {
18974
+ return null;
18975
+ }
18976
+ }
18977
+ function normalizeOrganization(value) {
18978
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
18979
+ }
18980
+
18760
18981
  // src/fitness-badge.ts
18761
18982
  var CHECK_LABELS = [
18762
18983
  { key: "spawn", label: "did not start" },
@@ -22933,9 +23154,9 @@ var scorecard_default = {
22933
23154
  schemaVersion: 1,
22934
23155
  runs: [
22935
23156
  {
22936
- id: "2026-08-11-m4max-llamacpp",
23157
+ id: "2026-08-13-m4max-llamacpp",
22937
23158
  provenance: {
22938
- startedAt: "2026-08-11T16:21:09.513Z",
23159
+ startedAt: "2026-08-13T07:05:36.620Z",
22939
23160
  device: {
22940
23161
  label: "Mac \xB7 Apple M4 Max",
22941
23162
  platform: "darwin",
@@ -22944,8 +23165,8 @@ var scorecard_default = {
22944
23165
  osRelease: "darwin 25.5.0",
22945
23166
  cpuModel: "Apple M4 Max"
22946
23167
  },
22947
- harnessCommit: "b2ea2819",
22948
- gildeVersion: "0.1.20",
23168
+ harnessCommit: "c5904085",
23169
+ gildeVersion: "0.1.23",
22949
23170
  count: 3,
22950
23171
  judgeModelId: null
22951
23172
  },
@@ -22982,57 +23203,978 @@ var scorecard_default = {
22982
23203
  }
22983
23204
  },
22984
23205
  {
22985
- id: "2026-08-09-m4max-llamacpp",
22986
- provenance: {
22987
- startedAt: "2026-08-09T23:39:13.000Z",
22988
- device: {
22989
- label: "Mac \xB7 Apple M4 Max",
22990
- platform: "darwin",
22991
- arch: "arm64",
22992
- memoryGb: 64,
22993
- osRelease: "darwin 25.5.0",
22994
- cpuModel: "Apple M4 Max"
23206
+ id: "2026-08-11-m4max-llamacpp",
23207
+ provenance: {
23208
+ startedAt: "2026-08-11T16:21:09.513Z",
23209
+ device: {
23210
+ label: "Mac \xB7 Apple M4 Max",
23211
+ platform: "darwin",
23212
+ arch: "arm64",
23213
+ memoryGb: 64,
23214
+ osRelease: "darwin 25.5.0",
23215
+ cpuModel: "Apple M4 Max"
23216
+ },
23217
+ harnessCommit: "b2ea2819",
23218
+ gildeVersion: "0.1.20",
23219
+ count: 3,
23220
+ judgeModelId: null
23221
+ },
23222
+ suites: ["core", "productivity"],
23223
+ scenariosBySuite: {
23224
+ core: [
23225
+ "tictactoe",
23226
+ "petshop",
23227
+ "tankcombat",
23228
+ "schema-migration",
23229
+ "failing-tests-spec",
23230
+ "symptom-debug",
23231
+ "data-wrangle",
23232
+ "incident-postmortem",
23233
+ "ops-runbook-anomaly",
23234
+ "plan-and-estimate",
23235
+ "conflict-synthesis"
23236
+ ],
23237
+ productivity: [
23238
+ "constrained-comms",
23239
+ "craftbook-week-plan",
23240
+ "craftbook-ab-test-readout",
23241
+ "craftbook-annotated-bibliography",
23242
+ "records-intake",
23243
+ "plan-and-estimate",
23244
+ "meeting-followup",
23245
+ "craftbook-spreadsheet-model",
23246
+ "conflict-synthesis",
23247
+ "docblocks-theme-roundtrip",
23248
+ "craftbook-research-to-document",
23249
+ "craftbook-powerpoint-deck",
23250
+ "wikipedia-research-brief"
23251
+ ]
23252
+ }
23253
+ },
23254
+ {
23255
+ id: "2026-08-09-m4max-llamacpp",
23256
+ provenance: {
23257
+ startedAt: "2026-08-09T23:39:13.000Z",
23258
+ device: {
23259
+ label: "Mac \xB7 Apple M4 Max",
23260
+ platform: "darwin",
23261
+ arch: "arm64",
23262
+ memoryGb: 64,
23263
+ osRelease: "darwin 25.5.0",
23264
+ cpuModel: "Apple M4 Max"
23265
+ },
23266
+ harnessCommit: "e2859602",
23267
+ gildeVersion: "0.1.17",
23268
+ count: 3,
23269
+ judgeModelId: null
23270
+ },
23271
+ suites: ["core", "productivity"],
23272
+ scenariosBySuite: {
23273
+ core: [
23274
+ "tictactoe",
23275
+ "petshop",
23276
+ "tankcombat",
23277
+ "schema-migration",
23278
+ "failing-tests-spec",
23279
+ "symptom-debug",
23280
+ "data-wrangle",
23281
+ "incident-postmortem",
23282
+ "ops-runbook-anomaly",
23283
+ "plan-and-estimate",
23284
+ "conflict-synthesis"
23285
+ ],
23286
+ productivity: [
23287
+ "constrained-comms",
23288
+ "craftbook-week-plan",
23289
+ "craftbook-ab-test-readout",
23290
+ "craftbook-annotated-bibliography",
23291
+ "records-intake",
23292
+ "plan-and-estimate",
23293
+ "meeting-followup",
23294
+ "craftbook-spreadsheet-model",
23295
+ "conflict-synthesis",
23296
+ "docblocks-theme-roundtrip",
23297
+ "craftbook-research-to-document",
23298
+ "craftbook-powerpoint-deck",
23299
+ "wikipedia-research-brief"
23300
+ ]
23301
+ },
23302
+ note: "Published scorecard sweep (llama-cpp, count-strict)."
23303
+ }
23304
+ ],
23305
+ results: [
23306
+ {
23307
+ modelId: "gemma4-26b-q4",
23308
+ label: "gemma4-26b-q4",
23309
+ engine: "llama-cpp",
23310
+ tier: "medium",
23311
+ parameterSize: "25.2B",
23312
+ runId: "2026-08-13-m4max-llamacpp",
23313
+ suiteId: "core",
23314
+ performance: {
23315
+ prefillTokensPerSec: 1264,
23316
+ decodeTokensPerSec: 107.5,
23317
+ samples: 1
23318
+ },
23319
+ runtime: {
23320
+ contextTokens: 120832,
23321
+ peakMemoryMb: 42690
23322
+ },
23323
+ judge: {
23324
+ meanScore: 5.7,
23325
+ artifacts: 9,
23326
+ judgeModel: "claude-sonnet-4-6"
23327
+ },
23328
+ cells: [
23329
+ {
23330
+ scenarioId: "tictactoe",
23331
+ trials: 3,
23332
+ successes: 3,
23333
+ nonModelFailures: 0,
23334
+ medianDurationMs: 127411
23335
+ },
23336
+ {
23337
+ scenarioId: "petshop",
23338
+ trials: 3,
23339
+ successes: 3,
23340
+ nonModelFailures: 0,
23341
+ medianDurationMs: 482159
23342
+ },
23343
+ {
23344
+ scenarioId: "tankcombat",
23345
+ trials: 3,
23346
+ successes: 3,
23347
+ nonModelFailures: 0,
23348
+ medianDurationMs: 290618
23349
+ },
23350
+ {
23351
+ scenarioId: "schema-migration",
23352
+ trials: 3,
23353
+ successes: 3,
23354
+ nonModelFailures: 0,
23355
+ medianDurationMs: 69039
23356
+ },
23357
+ {
23358
+ scenarioId: "failing-tests-spec",
23359
+ trials: 3,
23360
+ successes: 3,
23361
+ nonModelFailures: 0,
23362
+ medianDurationMs: 28770
23363
+ },
23364
+ {
23365
+ scenarioId: "symptom-debug",
23366
+ trials: 3,
23367
+ successes: 3,
23368
+ nonModelFailures: 0,
23369
+ medianDurationMs: 83940
23370
+ },
23371
+ {
23372
+ scenarioId: "data-wrangle",
23373
+ trials: 3,
23374
+ successes: 3,
23375
+ nonModelFailures: 0,
23376
+ medianDurationMs: 48055
23377
+ },
23378
+ {
23379
+ scenarioId: "incident-postmortem",
23380
+ trials: 3,
23381
+ successes: 3,
23382
+ nonModelFailures: 0,
23383
+ medianDurationMs: 189005
23384
+ },
23385
+ {
23386
+ scenarioId: "ops-runbook-anomaly",
23387
+ trials: 3,
23388
+ successes: 3,
23389
+ nonModelFailures: 0,
23390
+ medianDurationMs: 233624
23391
+ },
23392
+ {
23393
+ scenarioId: "plan-and-estimate",
23394
+ trials: 3,
23395
+ successes: 3,
23396
+ nonModelFailures: 0,
23397
+ medianDurationMs: 17679
23398
+ },
23399
+ {
23400
+ scenarioId: "conflict-synthesis",
23401
+ trials: 3,
23402
+ successes: 3,
23403
+ nonModelFailures: 0,
23404
+ medianDurationMs: 52710
23405
+ }
23406
+ ]
23407
+ },
23408
+ {
23409
+ modelId: "muse-glimmer-30b-q4",
23410
+ label: "muse-glimmer-30b-q4",
23411
+ engine: "llama-cpp",
23412
+ tier: "medium",
23413
+ parameterSize: "30B",
23414
+ runId: "2026-08-13-m4max-llamacpp",
23415
+ suiteId: "core",
23416
+ performance: {
23417
+ prefillTokensPerSec: 226,
23418
+ decodeTokensPerSec: 26,
23419
+ samples: 1
23420
+ },
23421
+ runtime: {
23422
+ contextTokens: 131072,
23423
+ peakMemoryMb: 19638
23424
+ },
23425
+ judge: {
23426
+ meanScore: 6.5,
23427
+ artifacts: 9,
23428
+ judgeModel: "claude-sonnet-4-6"
23429
+ },
23430
+ cells: [
23431
+ {
23432
+ scenarioId: "tictactoe",
23433
+ trials: 3,
23434
+ successes: 3,
23435
+ nonModelFailures: 0,
23436
+ medianDurationMs: 210652
23437
+ },
23438
+ {
23439
+ scenarioId: "petshop",
23440
+ trials: 3,
23441
+ successes: 3,
23442
+ nonModelFailures: 0,
23443
+ medianDurationMs: 356601
23444
+ },
23445
+ {
23446
+ scenarioId: "tankcombat",
23447
+ trials: 3,
23448
+ successes: 3,
23449
+ nonModelFailures: 0,
23450
+ medianDurationMs: 272117
23451
+ },
23452
+ {
23453
+ scenarioId: "schema-migration",
23454
+ trials: 3,
23455
+ successes: 3,
23456
+ nonModelFailures: 0,
23457
+ medianDurationMs: 269225
23458
+ },
23459
+ {
23460
+ scenarioId: "failing-tests-spec",
23461
+ trials: 3,
23462
+ successes: 3,
23463
+ nonModelFailures: 0,
23464
+ medianDurationMs: 279796
23465
+ },
23466
+ {
23467
+ scenarioId: "symptom-debug",
23468
+ trials: 3,
23469
+ successes: 3,
23470
+ nonModelFailures: 0,
23471
+ medianDurationMs: 239896
23472
+ },
23473
+ {
23474
+ scenarioId: "data-wrangle",
23475
+ trials: 3,
23476
+ successes: 3,
23477
+ nonModelFailures: 0,
23478
+ medianDurationMs: 563414
23479
+ },
23480
+ {
23481
+ scenarioId: "incident-postmortem",
23482
+ trials: 3,
23483
+ successes: 2,
23484
+ nonModelFailures: 0,
23485
+ medianDurationMs: 4467148
23486
+ },
23487
+ {
23488
+ scenarioId: "ops-runbook-anomaly",
23489
+ trials: 3,
23490
+ successes: 2,
23491
+ nonModelFailures: 0,
23492
+ medianDurationMs: 474336
23493
+ },
23494
+ {
23495
+ scenarioId: "plan-and-estimate",
23496
+ trials: 3,
23497
+ successes: 3,
23498
+ nonModelFailures: 0,
23499
+ medianDurationMs: 150465
23500
+ },
23501
+ {
23502
+ scenarioId: "conflict-synthesis",
23503
+ trials: 3,
23504
+ successes: 2,
23505
+ nonModelFailures: 0,
23506
+ medianDurationMs: 404097
23507
+ }
23508
+ ]
23509
+ },
23510
+ {
23511
+ modelId: "nemotron3.5-lightning-30b-q4",
23512
+ label: "nemotron3.5-lightning-30b-q4",
23513
+ engine: "llama-cpp",
23514
+ tier: "medium",
23515
+ parameterSize: "30B",
23516
+ runId: "2026-08-13-m4max-llamacpp",
23517
+ suiteId: "core",
23518
+ performance: {
23519
+ prefillTokensPerSec: 1030,
23520
+ decodeTokensPerSec: 93.2,
23521
+ samples: 1
23522
+ },
23523
+ runtime: {
23524
+ contextTokens: 1048576,
23525
+ peakMemoryMb: 30085
23526
+ },
23527
+ judge: {
23528
+ meanScore: 5.6,
23529
+ artifacts: 9,
23530
+ judgeModel: "claude-sonnet-4-6"
23531
+ },
23532
+ cells: [
23533
+ {
23534
+ scenarioId: "tictactoe",
23535
+ trials: 3,
23536
+ successes: 3,
23537
+ nonModelFailures: 0,
23538
+ medianDurationMs: 90179
23539
+ },
23540
+ {
23541
+ scenarioId: "petshop",
23542
+ trials: 3,
23543
+ successes: 3,
23544
+ nonModelFailures: 0,
23545
+ medianDurationMs: 899922
23546
+ },
23547
+ {
23548
+ scenarioId: "tankcombat",
23549
+ trials: 3,
23550
+ successes: 3,
23551
+ nonModelFailures: 0,
23552
+ medianDurationMs: 65485
23553
+ },
23554
+ {
23555
+ scenarioId: "schema-migration",
23556
+ trials: 3,
23557
+ successes: 3,
23558
+ nonModelFailures: 0,
23559
+ medianDurationMs: 133904
23560
+ },
23561
+ {
23562
+ scenarioId: "failing-tests-spec",
23563
+ trials: 3,
23564
+ successes: 2,
23565
+ nonModelFailures: 0,
23566
+ medianDurationMs: 230269
23567
+ },
23568
+ {
23569
+ scenarioId: "symptom-debug",
23570
+ trials: 3,
23571
+ successes: 3,
23572
+ nonModelFailures: 0,
23573
+ medianDurationMs: 94023
23574
+ },
23575
+ {
23576
+ scenarioId: "data-wrangle",
23577
+ trials: 3,
23578
+ successes: 2,
23579
+ nonModelFailures: 0,
23580
+ medianDurationMs: 319197
23581
+ },
23582
+ {
23583
+ scenarioId: "incident-postmortem",
23584
+ trials: 3,
23585
+ successes: 1,
23586
+ nonModelFailures: 0,
23587
+ medianDurationMs: 223455
23588
+ },
23589
+ {
23590
+ scenarioId: "ops-runbook-anomaly",
23591
+ trials: 3,
23592
+ successes: 3,
23593
+ nonModelFailures: 0,
23594
+ medianDurationMs: 93099
23595
+ },
23596
+ {
23597
+ scenarioId: "plan-and-estimate",
23598
+ trials: 3,
23599
+ successes: 2,
23600
+ nonModelFailures: 0,
23601
+ medianDurationMs: 68902
23602
+ },
23603
+ {
23604
+ scenarioId: "conflict-synthesis",
23605
+ trials: 3,
23606
+ successes: 1,
23607
+ nonModelFailures: 0,
23608
+ medianDurationMs: 254006
23609
+ }
23610
+ ]
23611
+ },
23612
+ {
23613
+ modelId: "qwen3.6-35b-a3b-q4",
23614
+ label: "qwen3.6-35b-a3b-q4",
23615
+ engine: "llama-cpp",
23616
+ tier: "medium",
23617
+ parameterSize: "35B",
23618
+ runId: "2026-08-13-m4max-llamacpp",
23619
+ suiteId: "core",
23620
+ performance: {
23621
+ prefillTokensPerSec: 1104,
23622
+ decodeTokensPerSec: 71.3,
23623
+ samples: 1
23624
+ },
23625
+ runtime: {
23626
+ contextTokens: 262144,
23627
+ peakMemoryMb: 26502
23628
+ },
23629
+ judge: {
23630
+ meanScore: 8.1,
23631
+ artifacts: 9,
23632
+ judgeModel: "claude-sonnet-4-6"
23633
+ },
23634
+ cells: [
23635
+ {
23636
+ scenarioId: "tictactoe",
23637
+ trials: 3,
23638
+ successes: 3,
23639
+ nonModelFailures: 0,
23640
+ medianDurationMs: 92213
23641
+ },
23642
+ {
23643
+ scenarioId: "petshop",
23644
+ trials: 3,
23645
+ successes: 3,
23646
+ nonModelFailures: 0,
23647
+ medianDurationMs: 162363
23648
+ },
23649
+ {
23650
+ scenarioId: "tankcombat",
23651
+ trials: 3,
23652
+ successes: 3,
23653
+ nonModelFailures: 0,
23654
+ medianDurationMs: 247709
23655
+ },
23656
+ {
23657
+ scenarioId: "schema-migration",
23658
+ trials: 3,
23659
+ successes: 2,
23660
+ nonModelFailures: 0,
23661
+ medianDurationMs: 186639
23662
+ },
23663
+ {
23664
+ scenarioId: "failing-tests-spec",
23665
+ trials: 3,
23666
+ successes: 3,
23667
+ nonModelFailures: 0,
23668
+ medianDurationMs: 28425
23669
+ },
23670
+ {
23671
+ scenarioId: "symptom-debug",
23672
+ trials: 3,
23673
+ successes: 3,
23674
+ nonModelFailures: 0,
23675
+ medianDurationMs: 37627
23676
+ },
23677
+ {
23678
+ scenarioId: "data-wrangle",
23679
+ trials: 3,
23680
+ successes: 2,
23681
+ nonModelFailures: 0,
23682
+ medianDurationMs: 47569
23683
+ },
23684
+ {
23685
+ scenarioId: "incident-postmortem",
23686
+ trials: 3,
23687
+ successes: 3,
23688
+ nonModelFailures: 0,
23689
+ medianDurationMs: 73048
23690
+ },
23691
+ {
23692
+ scenarioId: "ops-runbook-anomaly",
23693
+ trials: 3,
23694
+ successes: 3,
23695
+ nonModelFailures: 0,
23696
+ medianDurationMs: 98132
23697
+ },
23698
+ {
23699
+ scenarioId: "plan-and-estimate",
23700
+ trials: 3,
23701
+ successes: 3,
23702
+ nonModelFailures: 0,
23703
+ medianDurationMs: 27443
23704
+ },
23705
+ {
23706
+ scenarioId: "conflict-synthesis",
23707
+ trials: 3,
23708
+ successes: 2,
23709
+ nonModelFailures: 0,
23710
+ medianDurationMs: 82923
23711
+ }
23712
+ ]
23713
+ },
23714
+ {
23715
+ modelId: "gemma4-26b-q4",
23716
+ label: "gemma4-26b-q4",
23717
+ engine: "llama-cpp",
23718
+ tier: "medium",
23719
+ parameterSize: "25.2B",
23720
+ runId: "2026-08-13-m4max-llamacpp",
23721
+ suiteId: "productivity",
23722
+ performance: {
23723
+ prefillTokensPerSec: 1264,
23724
+ decodeTokensPerSec: 107.5,
23725
+ samples: 1
23726
+ },
23727
+ runtime: {
23728
+ contextTokens: 120832,
23729
+ peakMemoryMb: 41762
23730
+ },
23731
+ judge: {
23732
+ meanScore: 6,
23733
+ artifacts: 19,
23734
+ judgeModel: "claude-sonnet-4-6"
23735
+ },
23736
+ cells: [
23737
+ {
23738
+ scenarioId: "constrained-comms",
23739
+ trials: 3,
23740
+ successes: 3,
23741
+ nonModelFailures: 0,
23742
+ medianDurationMs: 22693
23743
+ },
23744
+ {
23745
+ scenarioId: "craftbook-week-plan",
23746
+ trials: 3,
23747
+ successes: 3,
23748
+ nonModelFailures: 0,
23749
+ medianDurationMs: 58001
23750
+ },
23751
+ {
23752
+ scenarioId: "craftbook-ab-test-readout",
23753
+ trials: 3,
23754
+ successes: 0,
23755
+ nonModelFailures: 0,
23756
+ medianDurationMs: 196546
23757
+ },
23758
+ {
23759
+ scenarioId: "craftbook-annotated-bibliography",
23760
+ trials: 3,
23761
+ successes: 3,
23762
+ nonModelFailures: 0,
23763
+ medianDurationMs: 33179
23764
+ },
23765
+ {
23766
+ scenarioId: "records-intake",
23767
+ trials: 3,
23768
+ successes: 3,
23769
+ nonModelFailures: 0,
23770
+ medianDurationMs: 58377
23771
+ },
23772
+ {
23773
+ scenarioId: "plan-and-estimate",
23774
+ trials: 3,
23775
+ successes: 3,
23776
+ nonModelFailures: 0,
23777
+ medianDurationMs: 17469
23778
+ },
23779
+ {
23780
+ scenarioId: "meeting-followup",
23781
+ trials: 3,
23782
+ successes: 3,
23783
+ nonModelFailures: 0,
23784
+ medianDurationMs: 48022
23785
+ },
23786
+ {
23787
+ scenarioId: "craftbook-spreadsheet-model",
23788
+ trials: 3,
23789
+ successes: 0,
23790
+ nonModelFailures: 0,
23791
+ medianDurationMs: 191950
23792
+ },
23793
+ {
23794
+ scenarioId: "conflict-synthesis",
23795
+ trials: 3,
23796
+ successes: 3,
23797
+ nonModelFailures: 0,
23798
+ medianDurationMs: 48088
23799
+ },
23800
+ {
23801
+ scenarioId: "docblocks-theme-roundtrip",
23802
+ trials: 3,
23803
+ successes: 3,
23804
+ nonModelFailures: 0,
23805
+ medianDurationMs: 27505
23806
+ },
23807
+ {
23808
+ scenarioId: "craftbook-research-to-document",
23809
+ trials: 3,
23810
+ successes: 3,
23811
+ nonModelFailures: 0,
23812
+ medianDurationMs: 32944
23813
+ },
23814
+ {
23815
+ scenarioId: "craftbook-powerpoint-deck",
23816
+ trials: 3,
23817
+ successes: 0,
23818
+ nonModelFailures: 0,
23819
+ medianDurationMs: 937028
23820
+ },
23821
+ {
23822
+ scenarioId: "wikipedia-research-brief",
23823
+ trials: 3,
23824
+ successes: 1,
23825
+ nonModelFailures: 0,
23826
+ medianDurationMs: 81272
23827
+ }
23828
+ ]
23829
+ },
23830
+ {
23831
+ modelId: "muse-glimmer-30b-q4",
23832
+ label: "muse-glimmer-30b-q4",
23833
+ engine: "llama-cpp",
23834
+ tier: "medium",
23835
+ parameterSize: "30B",
23836
+ runId: "2026-08-13-m4max-llamacpp",
23837
+ suiteId: "productivity",
23838
+ performance: {
23839
+ prefillTokensPerSec: 228,
23840
+ decodeTokensPerSec: 26,
23841
+ samples: 2
23842
+ },
23843
+ runtime: {
23844
+ contextTokens: 131072,
23845
+ peakMemoryMb: 19648
23846
+ },
23847
+ judge: {
23848
+ meanScore: 5.5,
23849
+ artifacts: 25,
23850
+ judgeModel: "claude-sonnet-4-6"
23851
+ },
23852
+ cells: [
23853
+ {
23854
+ scenarioId: "constrained-comms",
23855
+ trials: 3,
23856
+ successes: 3,
23857
+ nonModelFailures: 0,
23858
+ medianDurationMs: 168777
23859
+ },
23860
+ {
23861
+ scenarioId: "craftbook-week-plan",
23862
+ trials: 3,
23863
+ successes: 0,
23864
+ nonModelFailures: 0,
23865
+ medianDurationMs: 635096
23866
+ },
23867
+ {
23868
+ scenarioId: "craftbook-ab-test-readout",
23869
+ trials: 3,
23870
+ successes: 1,
23871
+ nonModelFailures: 0,
23872
+ medianDurationMs: 505316
23873
+ },
23874
+ {
23875
+ scenarioId: "craftbook-annotated-bibliography",
23876
+ trials: 3,
23877
+ successes: 3,
23878
+ nonModelFailures: 0,
23879
+ medianDurationMs: 153322
23880
+ },
23881
+ {
23882
+ scenarioId: "records-intake",
23883
+ trials: 3,
23884
+ successes: 2,
23885
+ nonModelFailures: 0,
23886
+ medianDurationMs: 2593201
23887
+ },
23888
+ {
23889
+ scenarioId: "plan-and-estimate",
23890
+ trials: 3,
23891
+ successes: 3,
23892
+ nonModelFailures: 0,
23893
+ medianDurationMs: 185158
23894
+ },
23895
+ {
23896
+ scenarioId: "meeting-followup",
23897
+ trials: 3,
23898
+ successes: 2,
23899
+ nonModelFailures: 0,
23900
+ medianDurationMs: 680419
23901
+ },
23902
+ {
23903
+ scenarioId: "craftbook-spreadsheet-model",
23904
+ trials: 3,
23905
+ successes: 0,
23906
+ nonModelFailures: 0,
23907
+ medianDurationMs: 541672
23908
+ },
23909
+ {
23910
+ scenarioId: "conflict-synthesis",
23911
+ trials: 3,
23912
+ successes: 2,
23913
+ nonModelFailures: 0,
23914
+ medianDurationMs: 344063
23915
+ },
23916
+ {
23917
+ scenarioId: "docblocks-theme-roundtrip",
23918
+ trials: 3,
23919
+ successes: 3,
23920
+ nonModelFailures: 0,
23921
+ medianDurationMs: 248997
23922
+ },
23923
+ {
23924
+ scenarioId: "craftbook-research-to-document",
23925
+ trials: 3,
23926
+ successes: 3,
23927
+ nonModelFailures: 0,
23928
+ medianDurationMs: 274010
23929
+ },
23930
+ {
23931
+ scenarioId: "craftbook-powerpoint-deck",
23932
+ trials: 3,
23933
+ successes: 0,
23934
+ nonModelFailures: 0,
23935
+ medianDurationMs: 3009529
23936
+ },
23937
+ {
23938
+ scenarioId: "wikipedia-research-brief",
23939
+ trials: 3,
23940
+ successes: 3,
23941
+ nonModelFailures: 0,
23942
+ medianDurationMs: 353289
23943
+ }
23944
+ ]
23945
+ },
23946
+ {
23947
+ modelId: "nemotron3.5-lightning-30b-q4",
23948
+ label: "nemotron3.5-lightning-30b-q4",
23949
+ engine: "llama-cpp",
23950
+ tier: "medium",
23951
+ parameterSize: "30B",
23952
+ runId: "2026-08-13-m4max-llamacpp",
23953
+ suiteId: "productivity",
23954
+ performance: {
23955
+ prefillTokensPerSec: 1030,
23956
+ decodeTokensPerSec: 93.2,
23957
+ samples: 1
23958
+ },
23959
+ runtime: {
23960
+ contextTokens: 1048576,
23961
+ peakMemoryMb: 29452
23962
+ },
23963
+ judge: {
23964
+ meanScore: 5.6,
23965
+ artifacts: 27,
23966
+ judgeModel: "claude-sonnet-4-6"
23967
+ },
23968
+ cells: [
23969
+ {
23970
+ scenarioId: "constrained-comms",
23971
+ trials: 3,
23972
+ successes: 0,
23973
+ nonModelFailures: 0,
23974
+ medianDurationMs: 91133
22995
23975
  },
22996
- harnessCommit: "e2859602",
22997
- gildeVersion: "0.1.17",
22998
- count: 3,
22999
- judgeModelId: null
23976
+ {
23977
+ scenarioId: "craftbook-week-plan",
23978
+ trials: 3,
23979
+ successes: 3,
23980
+ nonModelFailures: 0,
23981
+ medianDurationMs: 113088
23982
+ },
23983
+ {
23984
+ scenarioId: "craftbook-ab-test-readout",
23985
+ trials: 3,
23986
+ successes: 3,
23987
+ nonModelFailures: 0,
23988
+ medianDurationMs: 283896
23989
+ },
23990
+ {
23991
+ scenarioId: "craftbook-annotated-bibliography",
23992
+ trials: 3,
23993
+ successes: 3,
23994
+ nonModelFailures: 0,
23995
+ medianDurationMs: 73049
23996
+ },
23997
+ {
23998
+ scenarioId: "records-intake",
23999
+ trials: 3,
24000
+ successes: 1,
24001
+ nonModelFailures: 0,
24002
+ medianDurationMs: 395444
24003
+ },
24004
+ {
24005
+ scenarioId: "plan-and-estimate",
24006
+ trials: 3,
24007
+ successes: 1,
24008
+ nonModelFailures: 0,
24009
+ medianDurationMs: 105986
24010
+ },
24011
+ {
24012
+ scenarioId: "meeting-followup",
24013
+ trials: 3,
24014
+ successes: 3,
24015
+ nonModelFailures: 0,
24016
+ medianDurationMs: 113144
24017
+ },
24018
+ {
24019
+ scenarioId: "craftbook-spreadsheet-model",
24020
+ trials: 3,
24021
+ successes: 3,
24022
+ nonModelFailures: 0,
24023
+ medianDurationMs: 233654
24024
+ },
24025
+ {
24026
+ scenarioId: "conflict-synthesis",
24027
+ trials: 3,
24028
+ successes: 1,
24029
+ nonModelFailures: 0,
24030
+ medianDurationMs: 469600
24031
+ },
24032
+ {
24033
+ scenarioId: "docblocks-theme-roundtrip",
24034
+ trials: 3,
24035
+ successes: 1,
24036
+ nonModelFailures: 0,
24037
+ medianDurationMs: 109277
24038
+ },
24039
+ {
24040
+ scenarioId: "craftbook-research-to-document",
24041
+ trials: 3,
24042
+ successes: 3,
24043
+ nonModelFailures: 0,
24044
+ medianDurationMs: 130992
24045
+ },
24046
+ {
24047
+ scenarioId: "craftbook-powerpoint-deck",
24048
+ trials: 3,
24049
+ successes: 0,
24050
+ nonModelFailures: 0,
24051
+ medianDurationMs: 791082
24052
+ },
24053
+ {
24054
+ scenarioId: "wikipedia-research-brief",
24055
+ trials: 3,
24056
+ successes: 1,
24057
+ nonModelFailures: 0,
24058
+ medianDurationMs: 136065
24059
+ }
24060
+ ]
24061
+ },
24062
+ {
24063
+ modelId: "qwen3.6-35b-a3b-q4",
24064
+ label: "qwen3.6-35b-a3b-q4",
24065
+ engine: "llama-cpp",
24066
+ tier: "medium",
24067
+ parameterSize: "35B",
24068
+ runId: "2026-08-13-m4max-llamacpp",
24069
+ suiteId: "productivity",
24070
+ performance: {
24071
+ prefillTokensPerSec: 1104,
24072
+ decodeTokensPerSec: 71.3,
24073
+ samples: 1
23000
24074
  },
23001
- suites: ["core", "productivity"],
23002
- scenariosBySuite: {
23003
- core: [
23004
- "tictactoe",
23005
- "petshop",
23006
- "tankcombat",
23007
- "schema-migration",
23008
- "failing-tests-spec",
23009
- "symptom-debug",
23010
- "data-wrangle",
23011
- "incident-postmortem",
23012
- "ops-runbook-anomaly",
23013
- "plan-and-estimate",
23014
- "conflict-synthesis"
23015
- ],
23016
- productivity: [
23017
- "constrained-comms",
23018
- "craftbook-week-plan",
23019
- "craftbook-ab-test-readout",
23020
- "craftbook-annotated-bibliography",
23021
- "records-intake",
23022
- "plan-and-estimate",
23023
- "meeting-followup",
23024
- "craftbook-spreadsheet-model",
23025
- "conflict-synthesis",
23026
- "docblocks-theme-roundtrip",
23027
- "craftbook-research-to-document",
23028
- "craftbook-powerpoint-deck",
23029
- "wikipedia-research-brief"
23030
- ]
24075
+ runtime: {
24076
+ contextTokens: 262144,
24077
+ peakMemoryMb: 25657
23031
24078
  },
23032
- note: "Published scorecard sweep (llama-cpp, count-strict)."
23033
- }
23034
- ],
23035
- results: [
24079
+ judge: {
24080
+ meanScore: 6.6,
24081
+ artifacts: 27,
24082
+ judgeModel: "claude-sonnet-4-6"
24083
+ },
24084
+ cells: [
24085
+ {
24086
+ scenarioId: "constrained-comms",
24087
+ trials: 3,
24088
+ successes: 3,
24089
+ nonModelFailures: 0,
24090
+ medianDurationMs: 22314
24091
+ },
24092
+ {
24093
+ scenarioId: "craftbook-week-plan",
24094
+ trials: 3,
24095
+ successes: 3,
24096
+ nonModelFailures: 0,
24097
+ medianDurationMs: 88382
24098
+ },
24099
+ {
24100
+ scenarioId: "craftbook-ab-test-readout",
24101
+ trials: 3,
24102
+ successes: 3,
24103
+ nonModelFailures: 0,
24104
+ medianDurationMs: 303967
24105
+ },
24106
+ {
24107
+ scenarioId: "craftbook-annotated-bibliography",
24108
+ trials: 3,
24109
+ successes: 3,
24110
+ nonModelFailures: 0,
24111
+ medianDurationMs: 47530
24112
+ },
24113
+ {
24114
+ scenarioId: "records-intake",
24115
+ trials: 3,
24116
+ successes: 3,
24117
+ nonModelFailures: 0,
24118
+ medianDurationMs: 22365
24119
+ },
24120
+ {
24121
+ scenarioId: "plan-and-estimate",
24122
+ trials: 3,
24123
+ successes: 3,
24124
+ nonModelFailures: 0,
24125
+ medianDurationMs: 32491
24126
+ },
24127
+ {
24128
+ scenarioId: "meeting-followup",
24129
+ trials: 3,
24130
+ successes: 3,
24131
+ nonModelFailures: 0,
24132
+ medianDurationMs: 89751
24133
+ },
24134
+ {
24135
+ scenarioId: "craftbook-spreadsheet-model",
24136
+ trials: 3,
24137
+ successes: 3,
24138
+ nonModelFailures: 0,
24139
+ medianDurationMs: 228470
24140
+ },
24141
+ {
24142
+ scenarioId: "conflict-synthesis",
24143
+ trials: 3,
24144
+ successes: 1,
24145
+ nonModelFailures: 0,
24146
+ medianDurationMs: 211329
24147
+ },
24148
+ {
24149
+ scenarioId: "docblocks-theme-roundtrip",
24150
+ trials: 3,
24151
+ successes: 3,
24152
+ nonModelFailures: 0,
24153
+ medianDurationMs: 17446
24154
+ },
24155
+ {
24156
+ scenarioId: "craftbook-research-to-document",
24157
+ trials: 3,
24158
+ successes: 3,
24159
+ nonModelFailures: 0,
24160
+ medianDurationMs: 22407
24161
+ },
24162
+ {
24163
+ scenarioId: "craftbook-powerpoint-deck",
24164
+ trials: 3,
24165
+ successes: 1,
24166
+ nonModelFailures: 0,
24167
+ medianDurationMs: 1493404
24168
+ },
24169
+ {
24170
+ scenarioId: "wikipedia-research-brief",
24171
+ trials: 3,
24172
+ successes: 1,
24173
+ nonModelFailures: 0,
24174
+ medianDurationMs: 125222
24175
+ }
24176
+ ]
24177
+ },
23036
24178
  {
23037
24179
  modelId: "gemma4-26b-q4",
23038
24180
  label: "gemma4-26b-q4",
@@ -25379,6 +26521,11 @@ function collapseCraftbookForTier(book, opts) {
25379
26521
  0,
25380
26522
  ...members.map((m) => m.gate ? normalizeStepGate(m.gate).maxAttempts ?? 0 : 0)
25381
26523
  );
26524
+ const consumes = members.flatMap((member) => member.consumes ?? []).filter(
26525
+ (input, index, all) => all.findIndex(
26526
+ (candidate) => candidate.file === input.file && Boolean(candidate.artifact) === Boolean(input.artifact)
26527
+ ) === index
26528
+ );
25382
26529
  const isLast = groupIdx === groups.length - 1;
25383
26530
  const nextGroupAnchor = groups[groupIdx + 1];
25384
26531
  const nextId = nextGroupAnchor ? nextGroupAnchor.filter((m) => hasCompletionGate(m)).slice(-1)[0] ?? nextGroupAnchor[nextGroupAnchor.length - 1] : void 0;
@@ -25394,6 +26541,7 @@ function collapseCraftbookForTier(book, opts) {
25394
26541
  const step = {
25395
26542
  ...anchor,
25396
26543
  prompt: collapsedPrompt({ anchor, mergedNames, checks }),
26544
+ ...consumes.length > 0 ? { consumes } : { consumes: void 0 },
25397
26545
  gate,
25398
26546
  // Terminal + advanceWhen is an illegal combination; the terminal
25399
26547
  // group keeps only its completion gate.
@@ -25403,7 +26551,7 @@ function collapseCraftbookForTier(book, opts) {
25403
26551
  advanceWhen: anchor.advanceWhen ? { ...anchor.advanceWhen, goto: void 0 } : void 0
25404
26552
  }
25405
26553
  };
25406
- for (const key of ["terminal", "advanceWhen", "next"]) {
26554
+ for (const key of ["terminal", "advanceWhen", "next", "consumes"]) {
25407
26555
  if (step[key] === void 0) delete step[key];
25408
26556
  }
25409
26557
  if (step.advanceWhen && step.advanceWhen.goto === void 0) {
@@ -25542,6 +26690,16 @@ function docFromCraftbook(book) {
25542
26690
  };
25543
26691
  }
25544
26692
  function augmentGraphProblem(problem, stepIds) {
26693
+ const artifactInput = /^step "([^"]+)" consumes artifact "([^"]+)" but its prompt does not explicitly call `read_artifact`$/.exec(
26694
+ problem
26695
+ );
26696
+ if (artifactInput) {
26697
+ return {
26698
+ where: `steps (id "${artifactInput[1]}") \u2192 prompt`,
26699
+ message: `${problem}.`,
26700
+ fix: `start the procedure with \`read_artifact({ path: ${JSON.stringify(artifactInput[2])} })\`; artifact paths are not workspace paths`
26701
+ };
26702
+ }
25545
26703
  const missing = /"([^"]+)" missing from steps/.exec(problem);
25546
26704
  if (missing) {
25547
26705
  const near = nearestMatch(missing[1], stepIds);
@@ -26861,9 +28019,38 @@ function deriveThreadTitleFromMessages(messages, options = {}) {
26861
28019
  return starter ? deriveThreadTitle(starter.content) : null;
26862
28020
  }
26863
28021
 
28022
+ // src/catalog-work-in-progress.ts
28023
+ var CONNECTOR_PROJECT_TYPE_BASES = /* @__PURE__ */ new Set(["email", "social-media"]);
28024
+ function resolveShowWorkInProgressFeatures(configured, buildVersion) {
28025
+ return configured ?? buildVersion === "0.0.0";
28026
+ }
28027
+ function catalogItemUsesConnectors(item, connectorCraftbookIds2 = /* @__PURE__ */ new Set()) {
28028
+ const manifest = item.manifest;
28029
+ if (manifest.kind === "craftbook-template") {
28030
+ return (manifest.connectors?.length ?? 0) > 0;
28031
+ }
28032
+ if (manifest.kind !== "project-type") return false;
28033
+ if (manifest.extends && CONNECTOR_PROJECT_TYPE_BASES.has(manifest.extends)) return true;
28034
+ if ((manifest.craftbooks ?? []).some((id) => connectorCraftbookIds2.has(id))) return true;
28035
+ return (manifest.schedules ?? []).some(
28036
+ (schedule) => connectorCraftbookIds2.has(schedule.craftbook)
28037
+ );
28038
+ }
28039
+ function connectorCraftbookIds(items) {
28040
+ return new Set(
28041
+ items.flatMap(
28042
+ (item) => item.manifest.kind === "craftbook-template" && catalogItemUsesConnectors(item) ? [item.manifest.id] : []
28043
+ )
28044
+ );
28045
+ }
28046
+ function visibleCatalogItems(items, showWorkInProgressFeatures, connectorIds = connectorCraftbookIds(items)) {
28047
+ if (showWorkInProgressFeatures) return [...items];
28048
+ return items.filter((item) => !catalogItemUsesConnectors(item, connectorIds));
28049
+ }
28050
+
26864
28051
  // src/index.ts
26865
- var GEZEL_VERSION = "1.0.2";
26866
- var GEZEL_CONTENT_COMPAT = "1.26225";
28052
+ var GEZEL_VERSION = "1.0.4";
28053
+ var GEZEL_CONTENT_COMPAT = "1.26226";
26867
28054
  function nowIso() {
26868
28055
  return (/* @__PURE__ */ new Date()).toISOString();
26869
28056
  }
@@ -26990,6 +28177,7 @@ export {
26990
28177
  CraftbookSchema,
26991
28178
  CraftbookScriptsSchema,
26992
28179
  CraftbookSpawnSchema,
28180
+ CraftbookStepInputSchema,
26993
28181
  CraftbookStepSchema,
26994
28182
  CraftbookSuggestionSchema,
26995
28183
  CraftbookSummarySchema,
@@ -27895,6 +29083,7 @@ export {
27895
29083
  blockSize,
27896
29084
  buildIssueUrl,
27897
29085
  buildSuiteScoreboard,
29086
+ catalogItemUsesConnectors,
27898
29087
  cellAttributableTrials,
27899
29088
  classifySecurityLevel,
27900
29089
  coerceDeliverableKind,
@@ -27906,6 +29095,7 @@ export {
27906
29095
  composeFileContext,
27907
29096
  composeFitnessBadge,
27908
29097
  computeModelFit,
29098
+ connectorCraftbookIds,
27909
29099
  craftbookDocFormatFromEnv,
27910
29100
  craftbookFromDoc,
27911
29101
  craftbookRequirementsMet,
@@ -27948,6 +29138,7 @@ export {
27948
29138
  formatCraftbookDocErrors,
27949
29139
  formatErrorReport,
27950
29140
  formatJsonSchemaViolations,
29141
+ formatModelAttribution,
27951
29142
  formatNpmRegistrySpec,
27952
29143
  formatPassClaim,
27953
29144
  formatReviewProvenance,
@@ -28024,6 +29215,7 @@ export {
28024
29215
  meetsCapabilityFloor,
28025
29216
  mergeStreets,
28026
29217
  metaToFormValue,
29218
+ modelAttribution,
28027
29219
  modelFitnessKey,
28028
29220
  nearestFreeRect,
28029
29221
  nearestMatch,
@@ -28098,6 +29290,7 @@ export {
28098
29290
  resolveRoleId,
28099
29291
  resolveSandboxCopilot,
28100
29292
  resolveSecurityPolicy,
29293
+ resolveShowWorkInProgressFeatures,
28101
29294
  resolveSteps,
28102
29295
  retryTransient,
28103
29296
  rewriteGezelHrefs,
@@ -28154,6 +29347,7 @@ export {
28154
29347
  validateScriptInput,
28155
29348
  verifyBinaryDocumentBytes,
28156
29349
  videoMemoryBudgetBytes,
29350
+ visibleCatalogItems,
28157
29351
  workshopTempoDefaults,
28158
29352
  writeProcessOutput,
28159
29353
  xpForLevel,