halfcycle 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/bin.bundle.mjs +2031 -216
  3. package/commands/agent-teams-orchestration.md +1 -1
  4. package/commands/consistency-check.md +1 -1
  5. package/commands/context-update.md +1 -1
  6. package/commands/halfcycle-design.md +1 -1
  7. package/commands/halfcycle-setup.md +1 -1
  8. package/commands/stack-assembly-worker.md +1 -1
  9. package/commands/task-execution.md +1 -1
  10. package/commands/task-graph.md +1 -1
  11. package/commands/write-spec.md +1 -1
  12. package/dist/bin.d.ts +2 -1
  13. package/dist/bin.d.ts.map +1 -1
  14. package/dist/build-record/obtain.d.ts +14 -7
  15. package/dist/build-record/obtain.d.ts.map +1 -1
  16. package/dist/build-record/render-v2.d.ts +32 -6
  17. package/dist/build-record/render-v2.d.ts.map +1 -1
  18. package/dist/build-record/write.d.ts +2 -2
  19. package/dist/build-record/write.d.ts.map +1 -1
  20. package/dist/cli-contract.d.ts +2 -9
  21. package/dist/cli-contract.d.ts.map +1 -1
  22. package/dist/cli.js +3189 -1401
  23. package/dist/cli.js.map +3 -3
  24. package/dist/close-phase.d.ts +215 -134
  25. package/dist/close-phase.d.ts.map +1 -1
  26. package/dist/draw/index.d.ts +25 -5
  27. package/dist/draw/index.d.ts.map +1 -1
  28. package/dist/identity.d.ts +78 -9
  29. package/dist/identity.d.ts.map +1 -1
  30. package/dist/index.js +1624 -204
  31. package/dist/index.js.map +3 -3
  32. package/dist/install.d.ts +16 -0
  33. package/dist/install.d.ts.map +1 -1
  34. package/dist/open-phase.d.ts +25 -1
  35. package/dist/open-phase.d.ts.map +1 -1
  36. package/dist/plan.d.ts +59 -18
  37. package/dist/plan.d.ts.map +1 -1
  38. package/dist/pre-create-checks.d.ts.map +1 -1
  39. package/dist/review.d.ts +228 -0
  40. package/dist/review.d.ts.map +1 -0
  41. package/dist/shown-record.d.ts +46 -0
  42. package/dist/shown-record.d.ts.map +1 -0
  43. package/dist/system-report.d.ts +28 -4
  44. package/dist/system-report.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/project-page.d.ts +0 -24
  47. package/dist/project-page.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -238,6 +238,25 @@ var HALFCYCLE_COMMAND = "npx halfcycle@latest";
238
238
  // ../core/dist/release-version.js
239
239
  var RELEASE_VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
240
240
 
241
+ // ../core/dist/github-address.js
242
+ var GITHUB_REPOSITORY_ADDRESS = /^https:\/\/github\.com\/[A-Za-z0-9][A-Za-z0-9-]{0,38}\/(?!\.{1,2}$)[A-Za-z0-9._-]{1,100}$/;
243
+ var HOST = "[Gg][Ii][Tt][Hh][Uu][Bb]\\.[Cc][Oo][Mm]";
244
+ var OWNER = "[A-Za-z0-9][A-Za-z0-9-]{0,38}";
245
+ var RAW_REPOSITORY = "[A-Za-z0-9._-]{1,104}";
246
+ var TAIL = `(${OWNER})/(${RAW_REPOSITORY})/?$`;
247
+ var URL_USERINFO = "(?:[^\\s/?#@]+@)?";
248
+ var SSH_USER = "(?:[^\\s/?#@:]+@)?";
249
+ var REMOTE_FORMS = [
250
+ new RegExp(`^https?://${URL_USERINFO}${HOST}/${TAIL}`),
251
+ new RegExp(`^ssh://${SSH_USER}${HOST}/${TAIL}`),
252
+ new RegExp(`^git://${HOST}/${TAIL}`),
253
+ new RegExp(`^${SSH_USER}${HOST}:${TAIL}`)
254
+ ];
255
+
256
+ // ../core/dist/sign-in.js
257
+ var SIGN_IN_COMMAND = '"npx halfcycle"';
258
+ var HOOK_REMEDY = `Run ${SIGN_IN_COMMAND} in the project folder to sign in.`;
259
+
241
260
  // ../events/dist/result.js
242
261
  import { z as z2 } from "zod";
243
262
  var firedGuardSchema = z2.object({
@@ -305,17 +324,29 @@ function hasLoneSurrogate(text) {
305
324
  var LONE_SURROGATE_MESSAGE = "must be valid text: it holds half of a character pair with no other half";
306
325
 
307
326
  // ../events/dist/state.js
327
+ import { z as z4 } from "zod";
328
+
329
+ // ../events/dist/human-agent.js
308
330
  import { z as z3 } from "zod";
309
- var utcIso8601 = z3.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
331
+ var HUMAN_AGENT = "Human";
332
+ var AGENT_NAME_MAX = 32;
333
+ var agentNameSchema = z3.string().min(1).max(AGENT_NAME_MAX);
334
+
335
+ // ../events/dist/state.js
336
+ var utcIso8601 = z4.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
310
337
  var STATE_RECORD_FORMAT = "halfcycle-state-record/v1";
311
- var stepIdSchema = z3.string().regex(/^(l[0-5](-l[0-5])?|xl)\.[a-z0-9-]+$/);
312
- var methodVersionSchema = z3.string().regex(RELEASE_VERSION_PATTERN);
338
+ var stepIdSchema = z4.string().regex(/^(l[0-5](-l[0-5])?|xl)\.[a-z0-9-]+$/);
339
+ var methodVersionSchema = z4.string().regex(RELEASE_VERSION_PATTERN);
313
340
  var anchorPathRegex = /^(?!\/)(?!.*#)(?!method\/)(?!(.*\/)?docs\/method\/).+$/;
314
- var recordKindSchema = z3.enum(["artefact", "step-run", "gate", "intervention"]);
315
- var methodLayerSchema = z3.enum(["l0", "l1", "l2", "l3", "l3-l4", "l4", "l5", "xl"]);
316
- var evidenceKindSchema = z3.enum(["anchor", "diff", "repro", "question-answer", "no-op"]);
317
- var declarableEvidenceKindSchema = z3.enum(["anchor", "diff", "repro", "question-answer"]);
318
- var dispositionSchema = z3.enum([
341
+ var recordKindSchema = z4.enum(["artefact", "step-run", "gate", "intervention"]);
342
+ var methodLayerSchema = z4.enum(["l0", "l1", "l2", "l3", "l3-l4", "l4", "l5", "xl"]);
343
+ var PHASE_SCOPED_LAYERS = ["l4", "l5"];
344
+ function isPhaseScopedLayer(layer) {
345
+ return PHASE_SCOPED_LAYERS.includes(layer);
346
+ }
347
+ var evidenceKindSchema = z4.enum(["anchor", "diff", "repro", "question-answer", "no-op"]);
348
+ var declarableEvidenceKindSchema = z4.enum(["anchor", "diff", "repro", "question-answer"]);
349
+ var dispositionSchema = z4.enum([
319
350
  "prevented",
320
351
  "corrected-in-spec",
321
352
  "corrected-in-scope",
@@ -325,20 +356,20 @@ var dispositionSchema = z3.enum([
325
356
  "surfaced",
326
357
  "filed-forward"
327
358
  ]);
328
- var mechanismSchema = z3.enum(["guard", "verdict", "served-step", "method-step"]);
329
- var artefactStateSchema = z3.enum(["created", "updated"]);
359
+ var mechanismSchema = z4.enum(["guard", "verdict", "served-step", "method-step"]);
360
+ var artefactStateSchema = z4.enum(["created", "updated"]);
330
361
  var QUALIFIED_OUTCOMES = ["declined", "parked"];
331
- var qualifiedOutcomeSchema = z3.enum(QUALIFIED_OUTCOMES);
362
+ var qualifiedOutcomeSchema = z4.enum(QUALIFIED_OUTCOMES);
332
363
  function isQualifiedOutcome(outcome) {
333
364
  return QUALIFIED_OUTCOMES.includes(outcome);
334
365
  }
335
- var runOutcomeSchema = z3.enum([
366
+ var runOutcomeSchema = z4.enum([
336
367
  "completed",
337
368
  "attempted-failed",
338
369
  ...QUALIFIED_OUTCOMES
339
370
  ]);
340
- var gateKindSchema = z3.enum(["approval", "mechanical"]);
341
- var gateVerdictSchema = z3.enum(["pass", "fail", ...QUALIFIED_OUTCOMES]);
371
+ var gateKindSchema = z4.enum(["approval", "mechanical"]);
372
+ var gateVerdictSchema = z4.enum(["pass", "fail", ...QUALIFIED_OUTCOMES]);
342
373
  function outcomeReasonIssue(outcome, reason) {
343
374
  if (isQualifiedOutcome(outcome) && reason === void 0) {
344
375
  return { message: `outcomeReason is required when the outcome is '${outcome}'` };
@@ -350,47 +381,47 @@ function outcomeReasonIssue(outcome, reason) {
350
381
  }
351
382
  return null;
352
383
  }
353
- var anchorEvidenceSchema = z3.object({
354
- kind: z3.literal("anchor"),
355
- path: z3.string().regex(anchorPathRegex),
384
+ var anchorEvidenceSchema = z4.object({
385
+ kind: z4.literal("anchor"),
386
+ path: z4.string().regex(anchorPathRegex),
356
387
  // Optional 1-based line number within the file at `path`. Present when the
357
388
  // writer knows the exact line; omitted — never `0`, never `null` — when it
358
389
  // does not, because a line number is not always obtainable. Absence is a
359
390
  // valid, permanent state here, not a gap meant to be filled in later.
360
- line: z3.number().optional()
391
+ line: z4.number().optional()
361
392
  }).strict();
362
- var diffEvidenceSchema = z3.object({
363
- kind: z3.literal("diff"),
364
- ref: z3.string()
393
+ var diffEvidenceSchema = z4.object({
394
+ kind: z4.literal("diff"),
395
+ ref: z4.string()
365
396
  }).strict();
366
- var reproEvidenceSchema = z3.object({
367
- kind: z3.literal("repro"),
368
- steps: z3.string().max(2e3)
397
+ var reproEvidenceSchema = z4.object({
398
+ kind: z4.literal("repro"),
399
+ steps: z4.string().max(2e3)
369
400
  }).strict();
370
- var questionAnswerEvidenceSchema = z3.object({
371
- kind: z3.literal("question-answer"),
372
- question: z3.string().max(2e3),
373
- before: z3.string().max(2e3),
374
- after: z3.string().max(2e3)
401
+ var questionAnswerEvidenceSchema = z4.object({
402
+ kind: z4.literal("question-answer"),
403
+ question: z4.string().max(2e3),
404
+ before: z4.string().max(2e3),
405
+ after: z4.string().max(2e3)
375
406
  }).strict();
376
- var noOpEvidenceSchema = z3.object({
377
- kind: z3.literal("no-op"),
378
- inspected: z3.string().max(2e3),
379
- unchangedBecause: z3.string().max(2e3)
407
+ var noOpEvidenceSchema = z4.object({
408
+ kind: z4.literal("no-op"),
409
+ inspected: z4.string().max(2e3),
410
+ unchangedBecause: z4.string().max(2e3)
380
411
  }).strict();
381
- var evidenceSchema = z3.discriminatedUnion("kind", [
412
+ var evidenceSchema = z4.discriminatedUnion("kind", [
382
413
  anchorEvidenceSchema,
383
414
  diffEvidenceSchema,
384
415
  reproEvidenceSchema,
385
416
  questionAnswerEvidenceSchema,
386
417
  noOpEvidenceSchema
387
418
  ]);
388
- var stateRecordEnvelopeSchema = z3.object({
389
- format: z3.literal(STATE_RECORD_FORMAT),
419
+ var stateRecordEnvelopeSchema = z4.object({
420
+ format: z4.literal(STATE_RECORD_FORMAT),
390
421
  recordKind: recordKindSchema,
391
- recordId: z3.string().uuid(),
422
+ recordId: z4.string().uuid(),
392
423
  methodVersion: methodVersionSchema,
393
- engagementId: z3.string(),
424
+ engagementId: z4.string(),
394
425
  stepId: stepIdSchema,
395
426
  // Immutable: names when the thing happened, never when a later disposition
396
427
  // moved.
@@ -406,80 +437,94 @@ var stateRecordEnvelopeSchema = z3.object({
406
437
  // for the whole project rather than per phase. Absence is meaningful on its
407
438
  // own — it is never replaced with a placeholder value, and it is read as
408
439
  // "satisfies any phase", not as "unknown".
409
- phase: z3.string().optional()
440
+ phase: z4.string().optional()
410
441
  });
411
- var artefactRefSchema = z3.object({
412
- name: z3.string(),
413
- parent: z3.string()
442
+ var artefactRefSchema = z4.object({
443
+ name: z4.string(),
444
+ parent: z4.string()
414
445
  }).strict();
415
- var coverageSchema = z3.object({
416
- asked: z3.number().int().nonnegative(),
417
- answered: z3.number().int().nonnegative()
446
+ var coverageSchema = z4.object({
447
+ asked: z4.number().int().nonnegative(),
448
+ answered: z4.number().int().nonnegative()
418
449
  }).strict().superRefine((coverage, ctx) => {
419
450
  if (coverage.answered > coverage.asked) {
420
451
  ctx.addIssue({
421
- code: z3.ZodIssueCode.custom,
452
+ code: z4.ZodIssueCode.custom,
422
453
  path: ["answered"],
423
454
  message: "answered cannot be more than asked"
424
455
  });
425
456
  }
426
457
  });
458
+ var projectTierSchema = z4.number().int().min(0).max(3);
427
459
  var artefactRecordSchema = stateRecordEnvelopeSchema.extend({
428
- recordKind: z3.literal("artefact"),
460
+ recordKind: z4.literal("artefact"),
429
461
  artefactRef: artefactRefSchema,
430
462
  // Some layer wrote it, by construction — an empty array is not a legitimate
431
463
  // state, so the schema says so.
432
- writtenByLayers: z3.array(methodLayerSchema).min(1),
464
+ writtenByLayers: z4.array(methodLayerSchema).min(1),
433
465
  state: artefactStateSchema
434
466
  }).strict();
435
467
  var stepRunRecordSchema = stateRecordEnvelopeSchema.extend({
436
- recordKind: z3.literal("step-run"),
468
+ recordKind: z4.literal("step-run"),
437
469
  runOutcome: runOutcomeSchema,
438
- failureReason: z3.string().optional(),
439
- outcomeReason: z3.string().max(2e3).optional()
470
+ failureReason: z4.string().optional(),
471
+ outcomeReason: z4.string().max(2e3).optional(),
472
+ // Who did this step, as the session reported it. Absent when not reported.
473
+ agent: agentNameSchema.optional(),
474
+ // When the session began this step. Absent when not reported.
475
+ startedAt: utcIso8601.optional(),
476
+ // The project's tier, when this step settled it. Absent on every other step.
477
+ projectTier: projectTierSchema.optional()
440
478
  }).strict().superRefine((rec, ctx) => {
441
479
  if (rec.runOutcome === "attempted-failed" && rec.failureReason === void 0) {
442
480
  ctx.addIssue({
443
- code: z3.ZodIssueCode.custom,
481
+ code: z4.ZodIssueCode.custom,
444
482
  path: ["failureReason"],
445
483
  message: "failureReason is required when runOutcome is 'attempted-failed'"
446
484
  });
447
485
  }
448
486
  if (rec.runOutcome !== "attempted-failed" && rec.failureReason !== void 0) {
449
487
  ctx.addIssue({
450
- code: z3.ZodIssueCode.custom,
488
+ code: z4.ZodIssueCode.custom,
451
489
  path: ["failureReason"],
452
490
  message: "failureReason is only present when runOutcome is 'attempted-failed'"
453
491
  });
454
492
  }
455
493
  const issue = outcomeReasonIssue(rec.runOutcome, rec.outcomeReason);
456
494
  if (issue) {
457
- ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["outcomeReason"], ...issue });
495
+ ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["outcomeReason"], ...issue });
458
496
  }
459
497
  });
460
498
  var gateRecordSchema = stateRecordEnvelopeSchema.extend({
461
- recordKind: z3.literal("gate"),
499
+ recordKind: z4.literal("gate"),
462
500
  gateKind: gateKindSchema,
463
501
  verdict: gateVerdictSchema,
464
- actor: z3.string(),
465
- outcomeReason: z3.string().max(2e3).optional(),
466
- coverage: coverageSchema.optional()
502
+ actor: z4.string(),
503
+ outcomeReason: z4.string().max(2e3).optional(),
504
+ coverage: coverageSchema.optional(),
505
+ // Who did the step this checkpoint is on, as reported. Absent when not reported.
506
+ agent: agentNameSchema.optional(),
507
+ // When that step, or the review this checkpoint closes, began. Absent when not
508
+ // reported.
509
+ startedAt: utcIso8601.optional()
467
510
  }).strict().superRefine((rec, ctx) => {
468
511
  const issue = outcomeReasonIssue(rec.verdict, rec.outcomeReason);
469
512
  if (issue) {
470
- ctx.addIssue({ code: z3.ZodIssueCode.custom, path: ["outcomeReason"], ...issue });
513
+ ctx.addIssue({ code: z4.ZodIssueCode.custom, path: ["outcomeReason"], ...issue });
471
514
  }
472
515
  });
473
516
  var interventionRecordSchema = stateRecordEnvelopeSchema.extend({
474
- recordKind: z3.literal("intervention"),
517
+ recordKind: z4.literal("intervention"),
475
518
  layer: methodLayerSchema,
476
519
  mechanism: mechanismSchema,
477
520
  severity: severitySchema,
478
521
  disposition: dispositionSchema,
479
- summary: z3.string().max(2e3),
480
- coverage: coverageSchema.optional()
522
+ summary: z4.string().max(2e3),
523
+ coverage: coverageSchema.optional(),
524
+ // Who caught it, as the session reported. Absent when not reported.
525
+ agent: agentNameSchema.optional()
481
526
  }).strict();
482
- var stateRecordSchema = z3.discriminatedUnion("recordKind", [
527
+ var stateRecordSchema = z4.discriminatedUnion("recordKind", [
483
528
  artefactRecordSchema,
484
529
  stepRunRecordSchema,
485
530
  gateRecordSchema,
@@ -487,7 +532,7 @@ var stateRecordSchema = z3.discriminatedUnion("recordKind", [
487
532
  ]);
488
533
 
489
534
  // ../events/dist/build-record.js
490
- import { z as z4 } from "zod";
535
+ import { z as z5 } from "zod";
491
536
 
492
537
  // ../events/dist/control-characters.js
493
538
  var CONTROL_CHARACTER_CLASS = "\\p{Cc}";
@@ -577,11 +622,16 @@ var ACTOR_MAX = 200;
577
622
  var FINDING_MAX = 500;
578
623
  var FINDINGS_SHOWN_MAX = 50;
579
624
  var PHASE_CLOSE_REASON_MAX = 1900;
580
- var closeCommitSchema = z4.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash: 40 or 64 lowercase hex characters");
581
- var walkVerdictSchema = z4.enum(["clean", "defects"]);
582
- var closeDecisionSchema = z4.enum(["done-when-met", "override"]);
625
+ var closeCommitSchema = z5.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/, "must be a full commit hash: 40 or 64 lowercase hex characters");
626
+ var walkVerdictSchema = z5.enum(["clean", "defects"]);
627
+ var acceptanceOutcomeSchema = z5.object({
628
+ verdict: walkVerdictSchema,
629
+ findings: z5.number().int().nonnegative(),
630
+ findingsDetail: z5.array(z5.object({ summary: z5.string().refine((v) => !hasLoneSurrogate(v), { message: LONE_SURROGATE_MESSAGE }) }).strict())
631
+ }).strict();
632
+ var closeDecisionSchema = z5.enum(["done-when-met", "override"]);
583
633
  function plainText(max) {
584
- const base = max === void 0 ? z4.string() : z4.string().max(max);
634
+ const base = max === void 0 ? z5.string() : z5.string().max(max);
585
635
  return base.refine((v) => !hasControlCharacter(v), {
586
636
  message: "must be one line with no control characters (no line breaks, tabs or NUL)"
587
637
  }).refine((v) => !hasLoneSurrogate(v), { message: LONE_SURROGATE_MESSAGE });
@@ -593,81 +643,81 @@ function statementText(max) {
593
643
  }
594
644
  var closeActorSchema = plainText(ACTOR_MAX);
595
645
  var PHASE_NAME_RULE = `must be a phase name: not empty, no slash or backslash, not starting with a dot, no control characters, at most ${PHASE_NAME_MAX_BYTES} bytes`;
596
- var phaseNameSchema = z4.string().refine((v) => isValidPhaseIdentity(v), {
646
+ var phaseNameSchema = z5.string().refine((v) => isValidPhaseIdentity(v), {
597
647
  message: PHASE_NAME_RULE
598
648
  });
599
- var phaseIdentitySchema = z4.union([z4.string(), z4.number()], { error: "must be a phase name or a whole number" }).refine((v) => isValidPhaseIdentity(v), { message: PHASE_NAME_RULE });
600
- var closeStatementsSchema = z4.object({
649
+ var phaseIdentitySchema = z5.union([z5.string(), z5.number()], { error: "must be a phase name or a whole number" }).refine((v) => isValidPhaseIdentity(v), { message: PHASE_NAME_RULE });
650
+ var closeStatementsSchema = z5.object({
601
651
  thesis: statementText(STATEMENT_MAX).nullable(),
602
652
  outcome: statementText(STATEMENT_MAX).nullable(),
603
- deviations: z4.array(statementText(DEVIATION_MAX)).max(DEVIATIONS_MAX),
653
+ deviations: z5.array(statementText(DEVIATION_MAX)).max(DEVIATIONS_MAX),
604
654
  closeCommit: closeCommitSchema.nullable()
605
655
  }).strict();
606
- var closedBySchema = z4.discriminatedUnion("kind", [
607
- z4.object({ kind: z4.literal("account"), email: plainText().nullable() }).strict(),
608
- z4.object({ kind: z4.literal("no-account") }).strict(),
609
- z4.object({ kind: z4.literal("account-removed") }).strict(),
610
- z4.object({ kind: z4.literal("not-recorded") }).strict()
656
+ var closedBySchema = z5.discriminatedUnion("kind", [
657
+ z5.object({ kind: z5.literal("account"), email: plainText().nullable() }).strict(),
658
+ z5.object({ kind: z5.literal("no-account") }).strict(),
659
+ z5.object({ kind: z5.literal("account-removed") }).strict(),
660
+ z5.object({ kind: z5.literal("not-recorded") }).strict()
611
661
  ]);
612
662
  var STEP_NAME_MAX = 200;
613
663
  var stepNameSchema = plainText(STEP_NAME_MAX).refine((v) => v.trim().length > 0, {
614
664
  message: "must not be blank"
615
665
  });
616
- var unfinishedStepSchema = z4.object({
666
+ var unfinishedStepSchema = z5.object({
617
667
  stepId: plainText(),
618
- state: z4.enum(["parked", "failed"]),
668
+ state: z5.enum(["parked", "failed"]),
619
669
  name: stepNameSchema.optional()
620
670
  }).strict();
621
- var crossedSchema = z4.discriminatedUnion("kind", [
622
- z4.object({ kind: z4.literal("none") }).strict(),
623
- z4.object({ kind: z4.literal("unfinished-steps"), steps: z4.array(unfinishedStepSchema).min(1) }).strict(),
624
- z4.object({ kind: z4.literal("clean-over-findings"), findings: z4.number().int().min(1) }).strict(),
625
- z4.object({ kind: z4.literal("not-recorded") }).strict()
671
+ var crossedSchema = z5.discriminatedUnion("kind", [
672
+ z5.object({ kind: z5.literal("none") }).strict(),
673
+ z5.object({ kind: z5.literal("unfinished-steps"), steps: z5.array(unfinishedStepSchema).min(1) }).strict(),
674
+ z5.object({ kind: z5.literal("clean-over-findings"), findings: z5.number().int().min(1) }).strict(),
675
+ z5.object({ kind: z5.literal("not-recorded") }).strict()
626
676
  ]);
627
- var seatCoverageSchema = z4.object({
628
- enrolled: z4.number().int().nonnegative(),
629
- conformant: z4.number().int().nonnegative()
677
+ var seatCoverageSchema = z5.object({
678
+ enrolled: z5.number().int().nonnegative(),
679
+ conformant: z5.number().int().nonnegative()
630
680
  }).strict().refine((c) => c.conformant <= c.enrolled, {
631
681
  message: "conformant cannot exceed enrolled",
632
682
  path: ["conformant"]
633
683
  });
634
- var observedCloseSchema = z4.object({
635
- sequence: z4.number().int().min(1),
684
+ var observedCloseSchema = z5.object({
685
+ sequence: z5.number().int().min(1),
636
686
  closedAt: utcIso8601,
637
687
  closedBy: closedBySchema,
638
688
  crossed: crossedSchema,
639
689
  seatCoverage: seatCoverageSchema.nullable()
640
690
  }).strict();
641
- var couldNotRunCheckSchema = z4.object({
691
+ var couldNotRunCheckSchema = z5.object({
642
692
  check: plainText(),
643
693
  description: plainText().nullable(),
644
694
  reason: plainText()
645
695
  }).strict();
646
- var firedCheckSchema = z4.object({
696
+ var firedCheckSchema = z5.object({
647
697
  check: plainText(),
648
698
  description: plainText().nullable(),
649
699
  severity: severitySchema,
650
700
  explanation: plainText().nullable(),
651
- occurrences: z4.number().int().min(1)
701
+ occurrences: z5.number().int().min(1)
652
702
  }).strict();
653
- var phaseChecksSchema = z4.discriminatedUnion("state", [
654
- z4.object({ state: z4.literal("not-observed") }).strict(),
655
- z4.object({
656
- state: z4.literal("nothing-fired"),
657
- runs: z4.number().int().min(1),
658
- couldNotRun: z4.array(couldNotRunCheckSchema)
703
+ var phaseChecksSchema = z5.discriminatedUnion("state", [
704
+ z5.object({ state: z5.literal("not-observed") }).strict(),
705
+ z5.object({
706
+ state: z5.literal("nothing-fired"),
707
+ runs: z5.number().int().min(1),
708
+ couldNotRun: z5.array(couldNotRunCheckSchema)
659
709
  }).strict(),
660
- z4.object({
661
- state: z4.literal("fired"),
662
- runs: z4.number().int().min(1),
663
- couldNotRun: z4.array(couldNotRunCheckSchema),
664
- fired: z4.array(firedCheckSchema).min(1)
710
+ z5.object({
711
+ state: z5.literal("fired"),
712
+ runs: z5.number().int().min(1),
713
+ couldNotRun: z5.array(couldNotRunCheckSchema),
714
+ fired: z5.array(firedCheckSchema).min(1)
665
715
  }).strict()
666
716
  ]);
667
- var observedSchema = z4.object({
717
+ var observedSchema = z5.object({
668
718
  engagementId: plainText(),
669
719
  methodVersion: methodVersionSchema,
670
- closes: z4.array(observedCloseSchema).min(1),
720
+ closes: z5.array(observedCloseSchema).min(1),
671
721
  checks: phaseChecksSchema
672
722
  }).strict().superRefine((o, ctx) => {
673
723
  o.closes.forEach((close, i) => {
@@ -680,18 +730,18 @@ var observedSchema = z4.object({
680
730
  }
681
731
  });
682
732
  });
683
- var attestedWalkSchema = z4.object({
733
+ var attestedWalkSchema = z5.object({
684
734
  verdict: walkVerdictSchema,
685
- findings: z4.array(plainText(FINDING_MAX)).max(FINDINGS_SHOWN_MAX),
686
- findingsNotShown: z4.number().int().nonnegative()
735
+ findings: z5.array(plainText(FINDING_MAX)).max(FINDINGS_SHOWN_MAX),
736
+ findingsNotShown: z5.number().int().nonnegative()
687
737
  }).strict();
688
- var attestedStatementsSchema = z4.object({
738
+ var attestedStatementsSchema = z5.object({
689
739
  thesis: plainText(STATEMENT_MAX).nullable(),
690
740
  outcome: plainText(STATEMENT_MAX).nullable(),
691
- deviations: z4.array(plainText(DEVIATION_MAX)).max(DEVIATIONS_MAX)
741
+ deviations: z5.array(plainText(DEVIATION_MAX)).max(DEVIATIONS_MAX)
692
742
  }).strict();
693
- var attestedCloseSchema = z4.object({
694
- sequence: z4.number().int().min(1),
743
+ var attestedCloseSchema = z5.object({
744
+ sequence: z5.number().int().min(1),
695
745
  decision: closeDecisionSchema,
696
746
  actor: plainText(ACTOR_MAX).nullable(),
697
747
  reason: plainText(PHASE_CLOSE_REASON_MAX).nullable(),
@@ -699,15 +749,10 @@ var attestedCloseSchema = z4.object({
699
749
  closeCommit: closeCommitSchema.nullable(),
700
750
  statements: attestedStatementsSchema
701
751
  }).strict();
702
- var attestedSchema = z4.object({
703
- closes: z4.array(attestedCloseSchema)
752
+ var attestedSchema = z5.object({
753
+ closes: z5.array(attestedCloseSchema)
704
754
  }).strict();
705
- var buildRecordV2Schema = z4.object({
706
- format: z4.literal(BUILD_RECORD_V2_FORMAT),
707
- phase: phaseNameSchema,
708
- observed: observedSchema,
709
- attested: attestedSchema
710
- }).strict().superRefine((record2, ctx) => {
755
+ function checkRecordHalves(record2, ctx) {
711
756
  const observedBySequence = new Map(record2.observed.closes.map((c) => [c.sequence, c]));
712
757
  const attestedSequences = record2.attested.closes.map((c) => c.sequence);
713
758
  const sameSet = attestedSequences.length === observedBySequence.size && new Set(attestedSequences).size === attestedSequences.length && attestedSequences.every((s) => observedBySequence.has(s));
@@ -735,18 +780,24 @@ var buildRecordV2Schema = z4.object({
735
780
  });
736
781
  }
737
782
  });
738
- });
783
+ }
784
+ var buildRecordV2Schema = z5.object({
785
+ format: z5.literal(BUILD_RECORD_V2_FORMAT),
786
+ phase: phaseNameSchema,
787
+ observed: observedSchema,
788
+ attested: attestedSchema
789
+ }).strict().superRefine(checkRecordHalves);
739
790
 
740
791
  // ../events/dist/crew.js
741
- import { z as z5 } from "zod";
742
- var crewGroupSchema = z5.enum(["builder", "reviewer", "always-on"]);
743
- var crewTierSchema = z5.enum(["deep", "standard", "fast"]);
792
+ import { z as z6 } from "zod";
793
+ var crewGroupSchema = z6.enum(["builder", "reviewer", "always-on"]);
794
+ var crewTierSchema = z6.enum(["deep", "standard", "fast"]);
744
795
  var CREW_DISCIPLINE_MAX = 64;
745
- var crewFaceHeadSchema = z5.enum(["sq", "sqr", "rnd", "dome", "hex"]);
746
- var crewFaceAntennaSchema = z5.enum(["none", "stalk", "twin", "dish"]);
747
- var crewFaceEyesSchema = z5.enum(["dots", "bars", "visor", "cyclops"]);
748
- var crewFaceMouthSchema = z5.enum(["line", "grid", "dots", "wave"]);
749
- var crewFaceSchema = z5.object({
796
+ var crewFaceHeadSchema = z6.enum(["sq", "sqr", "rnd", "dome", "hex"]);
797
+ var crewFaceAntennaSchema = z6.enum(["none", "stalk", "twin", "dish"]);
798
+ var crewFaceEyesSchema = z6.enum(["dots", "bars", "visor", "cyclops"]);
799
+ var crewFaceMouthSchema = z6.enum(["line", "grid", "dots", "wave"]);
800
+ var crewFaceSchema = z6.object({
750
801
  // The outline of the head. Members who do the same kind of work share one, so a
751
802
  // team is recognisable before any name is read.
752
803
  head: crewFaceHeadSchema,
@@ -757,17 +808,17 @@ var crewFaceSchema = z5.object({
757
808
  // The mouth.
758
809
  mouth: crewFaceMouthSchema,
759
810
  // Whether this member is drawn with ears.
760
- ears: z5.boolean()
811
+ ears: z6.boolean()
761
812
  }).strict();
762
- var crewMemberSchema = z5.object({
813
+ var crewMemberSchema = z6.object({
763
814
  // The agent's name. The same twenty-one on every project, so a name is a
764
815
  // vocabulary a returning client already knows.
765
- callsign: z5.string().min(1).max(32),
816
+ callsign: z6.string().min(1).max(32),
766
817
  // Which of the three headings this member reads under.
767
818
  group: crewGroupSchema,
768
819
  // The kind of work this member takes. A task names a discipline, and only
769
820
  // agents holding it can be dispatched to it.
770
- discipline: z5.string().min(1).max(CREW_DISCIPLINE_MAX),
821
+ discipline: z6.string().min(1).max(CREW_DISCIPLINE_MAX),
771
822
  // How much thinking this member brings. There is no field naming the supplier
772
823
  // behind it, on this shape or on any other.
773
824
  tier: crewTierSchema,
@@ -930,10 +981,1199 @@ var CREW_ROSTER = [
930
981
  ];
931
982
  var CREW_ROSTER_SIZE = CREW_ROSTER.length;
932
983
  var CREW_CALLSIGNS = CREW_ROSTER.map((member) => member.callsign);
984
+ var crewRoleSchema = z6.enum([
985
+ "business-analyst",
986
+ "architect",
987
+ "spec-writer",
988
+ "builder",
989
+ "context-docs",
990
+ "spec-reviewer",
991
+ "code-test-reviewer",
992
+ "delivery-lead",
993
+ "quality-lead"
994
+ ]);
995
+ var crewLayerSchema = methodLayerSchema.extract(["l0", "l1", "l2", "l3", "l4", "l5"]);
996
+ var crewRoleLayersSchema = z6.object({
997
+ leads: z6.array(crewLayerSchema),
998
+ helps: z6.array(crewLayerSchema)
999
+ }).strict();
1000
+
1001
+ // ../events/dist/build-snapshot.js
1002
+ import { z as z8 } from "zod";
1003
+
1004
+ // ../events/dist/system-description.js
1005
+ import { z as z7 } from "zod";
1006
+ var SYSTEM_DESCRIPTION_FORMAT = 1;
1007
+ var SYSTEM_PARTS_MAX = 60;
1008
+ var SYSTEM_CONNECTIONS_MAX = 400;
1009
+ var SYSTEM_SLOT_MAX = 11;
1010
+ var SYSTEM_OWNS_MAX = 20;
1011
+ var SYSTEM_OWNED_PATTERN_MAX = 200;
1012
+ var SYSTEM_PART_NAME_MAX = 60;
1013
+ var SYSTEM_PART_DESCRIPTION_MAX = 160;
1014
+ var SYSTEM_CONNECTION_LABEL_MAX = 60;
1015
+ var SYSTEM_PART_ID_PATTERN = /^[a-z][a-z0-9-]{0,39}$/;
1016
+ var SYSTEM_LANES = ["clients", "edge", "services", "data", "external"];
1017
+ var systemLaneSchema = z7.enum(SYSTEM_LANES, {
1018
+ error: `must be one of ${SYSTEM_LANES.join(", ")}`
1019
+ });
1020
+ var SYSTEM_CONNECTION_KINDS = ["call", "event", "write", "external"];
1021
+ var systemConnectionKindSchema = z7.enum(SYSTEM_CONNECTION_KINDS, {
1022
+ error: `must be one of ${SYSTEM_CONNECTION_KINDS.join(", ")}`
1023
+ });
1024
+ var LINE_SEPARATOR = /[\u2028\u2029]/;
1025
+ function descriptionText(max) {
1026
+ return statementText(max).refine((v) => v.trim().length > 0, { message: "must not be empty or only spaces" }).refine((v) => !LINE_SEPARATOR.test(v), {
1027
+ message: "must be one line: it holds a line or paragraph separator"
1028
+ });
1029
+ }
1030
+ var systemPartIdSchema = z7.string().regex(SYSTEM_PART_ID_PATTERN, {
1031
+ message: "must be a short id: a lowercase letter, then lowercase letters, digits or hyphens, 40 characters at most"
1032
+ }).refine((v) => !looksLikeSecret(v), { message: "looks like a password or key; choose another id" });
1033
+ function hasEmptySegment(pattern) {
1034
+ return pattern.split("/").some((segment) => segment.length === 0);
1035
+ }
1036
+ function hasDotSegment(pattern) {
1037
+ return pattern.split("/").some((segment) => segment === "." || segment === "..");
1038
+ }
1039
+ function hasPartialDoubleStar(pattern) {
1040
+ return pattern.split("/").some((segment) => segment.includes("**") && segment !== "**");
1041
+ }
1042
+ var SEGMENT_CHARACTERS = /^[A-Za-z0-9._\-*]*$/;
1043
+ function hasForeignCharacter(pattern) {
1044
+ return pattern.split("/").some((segment) => !SEGMENT_CHARACTERS.test(segment));
1045
+ }
1046
+ var systemOwnedPatternSchema = z7.string().max(SYSTEM_OWNED_PATTERN_MAX, { message: `must be at most ${SYSTEM_OWNED_PATTERN_MAX} characters` }).refine((v) => !hasEmptySegment(v), {
1047
+ message: "must not start or end with a slash, or have two slashes together"
1048
+ }).refine((v) => !hasDotSegment(v), { message: 'must not have a "." or ".." folder in it' }).refine((v) => !hasPartialDoubleStar(v), { message: '"**" must stand alone between slashes' }).refine((v) => !hasForeignCharacter(v), {
1049
+ message: 'may use only letters, digits, ".", "_", "-" and "*" between slashes'
1050
+ }).refine((v) => !looksLikeSecret(v), {
1051
+ message: "this path looks like a credential; list a shorter folder above it"
1052
+ });
1053
+ var systemPartSchema = z7.object({
1054
+ id: systemPartIdSchema,
1055
+ name: descriptionText(SYSTEM_PART_NAME_MAX),
1056
+ description: descriptionText(SYSTEM_PART_DESCRIPTION_MAX),
1057
+ lane: systemLaneSchema,
1058
+ slot: z7.number({ error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }).int({ error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }).min(0, { error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }).max(SYSTEM_SLOT_MAX, { error: `must be a whole number from 0 to ${SYSTEM_SLOT_MAX}` }),
1059
+ outside: z7.boolean(),
1060
+ owns: z7.array(systemOwnedPatternSchema).max(SYSTEM_OWNS_MAX, { message: `can list at most ${SYSTEM_OWNS_MAX} paths` })
1061
+ }).strict();
1062
+ var systemConnectionSchema = z7.object({
1063
+ from: systemPartIdSchema,
1064
+ to: systemPartIdSchema,
1065
+ kind: systemConnectionKindSchema,
1066
+ label: descriptionText(SYSTEM_CONNECTION_LABEL_MAX)
1067
+ }).strict();
1068
+ var systemDescriptionSchema = z7.object({
1069
+ format: z7.literal(SYSTEM_DESCRIPTION_FORMAT, { error: `must be ${SYSTEM_DESCRIPTION_FORMAT}` }),
1070
+ parts: z7.array(systemPartSchema).min(1, { message: "must list at least one part" }).max(SYSTEM_PARTS_MAX, { message: `can hold at most ${SYSTEM_PARTS_MAX} parts (five lanes of twelve slots)` }),
1071
+ connections: z7.array(systemConnectionSchema).max(SYSTEM_CONNECTIONS_MAX, { message: `can hold at most ${SYSTEM_CONNECTIONS_MAX} connections` })
1072
+ }).strict();
1073
+ var systemDescriptionProblemSchema = z7.object({
1074
+ path: z7.string(),
1075
+ message: z7.string()
1076
+ }).strict();
1077
+ var systemReportRecordedSchema = z7.object({
1078
+ outcome: z7.literal("recorded"),
1079
+ version: z7.number().int().min(1),
1080
+ reportedAt: utcIso8601
1081
+ }).strict();
1082
+ var systemReportRefusedSchema = z7.object({
1083
+ outcome: z7.literal("refused"),
1084
+ problems: z7.array(systemDescriptionProblemSchema).min(1)
1085
+ }).strict();
1086
+ var systemReportUnavailableSchema = z7.object({
1087
+ outcome: z7.literal("unavailable")
1088
+ }).strict();
1089
+ var systemReportResultSchema = z7.discriminatedUnion("outcome", [
1090
+ systemReportRecordedSchema,
1091
+ systemReportRefusedSchema,
1092
+ systemReportUnavailableSchema
1093
+ ]);
1094
+ var SYSTEM_DRAW_SVG_MAX_BYTES = 2 * 1024 * 1024;
1095
+ var SYSTEM_DRAW_MERMAID_MAX_BYTES = 256 * 1024;
1096
+ var SYSTEM_DRAW_PNG_MAX_BYTES = 8 * 1024 * 1024;
1097
+ var systemDrawArchitectureRequestSchema = z7.object({
1098
+ picture: z7.literal("architecture"),
1099
+ version: z7.number().int().min(1)
1100
+ }).strict();
1101
+ var systemDrawPhaseRequestSchema = z7.object({
1102
+ picture: z7.literal("phase"),
1103
+ phase: phaseIdentitySchema,
1104
+ planVersion: z7.number().int().min(1)
1105
+ }).strict();
1106
+ var systemDrawClosedPhaseRequestSchema = z7.object({
1107
+ picture: z7.literal("closed-phase"),
1108
+ phase: phaseIdentitySchema
1109
+ }).strict();
1110
+ var systemDrawRequestSchema = z7.discriminatedUnion("picture", [
1111
+ systemDrawArchitectureRequestSchema,
1112
+ systemDrawPhaseRequestSchema,
1113
+ systemDrawClosedPhaseRequestSchema
1114
+ ]);
1115
+ function utf8ByteLength2(text) {
1116
+ let bytes = 0;
1117
+ for (let i = 0; i < text.length; i++) {
1118
+ const unit = text.charCodeAt(i);
1119
+ if (unit < 128)
1120
+ bytes += 1;
1121
+ else if (unit < 2048)
1122
+ bytes += 2;
1123
+ else if (unit >= 55296 && unit <= 56319 && i + 1 < text.length) {
1124
+ const next = text.charCodeAt(i + 1);
1125
+ if (next >= 56320 && next <= 57343) {
1126
+ bytes += 4;
1127
+ i++;
1128
+ } else {
1129
+ bytes += 3;
1130
+ }
1131
+ } else
1132
+ bytes += 3;
1133
+ }
1134
+ return bytes;
1135
+ }
1136
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
1137
+ function base64DecodedLength(text) {
1138
+ const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
1139
+ return text.length / 4 * 3 - padding;
1140
+ }
1141
+ var systemPictureSchema = z7.object({
1142
+ svg: z7.string().min(1).refine((v) => utf8ByteLength2(v) <= SYSTEM_DRAW_SVG_MAX_BYTES, { message: "the picture is larger than 2 MB" }),
1143
+ png: z7.string().min(1).refine((v) => v.length % 4 === 0 && BASE64.test(v), { message: "the image is not valid base64" }).refine((v) => base64DecodedLength(v) <= SYSTEM_DRAW_PNG_MAX_BYTES, { message: "the image is larger than 8 MB" })
1144
+ }).strict();
1145
+ var systemDrawRefusedSchema = z7.object({
1146
+ outcome: z7.literal("refused"),
1147
+ reason: z7.string().min(1)
1148
+ }).strict();
1149
+ var systemDrawUnavailableSchema = z7.object({
1150
+ outcome: z7.literal("unavailable")
1151
+ }).strict();
1152
+ var systemDrawNotClosedSchema = z7.object({
1153
+ outcome: z7.literal("not-closed")
1154
+ }).strict();
1155
+
1156
+ // ../events/dist/build-snapshot.js
1157
+ var buildTaskStateSchema = z8.enum([
1158
+ "pending",
1159
+ "in-flight",
1160
+ "completed",
1161
+ "failed",
1162
+ "blocked"
1163
+ ]);
1164
+ var BUILD_TASK_TITLE_MAX = 240;
1165
+ var BUILD_SNAPSHOT_TASK_LIMIT = 500;
1166
+ var BUILD_TASK_WHY_OUTSIDE_PLAN_MAX = 160;
1167
+ var BUILD_TASK_BUDGET_MAX_MINUTES = 10080;
1168
+ var BUILD_TASK_NOTE_MAX = 240;
1169
+ var BUILD_TASK_CLEAR_COMMAND_MAX = 240;
1170
+ var buildTaskKindSchema = z8.enum(["build", "review", "spec", "walk"]);
1171
+ var buildTaskIdSchema = z8.string().min(1).max(64);
1172
+ var buildTaskSchema = z8.object({
1173
+ // The task's own id, as the coordinator's graph names it.
1174
+ id: buildTaskIdSchema,
1175
+ // What the task is, in the coordinator's words. A description of the work,
1176
+ // bounded and scanned — never an excerpt of anybody's code.
1177
+ title: z8.string().min(1).max(BUILD_TASK_TITLE_MAX),
1178
+ // What the task is doing right now.
1179
+ state: buildTaskStateSchema,
1180
+ // Who holds it — a bare callsign, or the value naming a person when the work is
1181
+ // a person's. Absent when nothing has been dispatched to it yet.
1182
+ agent: agentNameSchema.optional(),
1183
+ // The kind of work the holder does. Present exactly when an agent holds it.
1184
+ discipline: z8.string().min(1).max(64).optional(),
1185
+ // How much thinking the holder brings. Present exactly when an agent holds it,
1186
+ // and there is no field naming the supplier behind it.
1187
+ tier: z8.string().min(1).max(32).optional(),
1188
+ // The tasks this one cannot start before. Always present; empty means it waits
1189
+ // on nothing, which is different from not having been computed.
1190
+ dependsOn: z8.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
1191
+ // The one task this task checks, when it is a check on other work. Absent on a
1192
+ // task that is doing the work rather than checking it.
1193
+ reviews: buildTaskIdSchema.optional(),
1194
+ // Which wave of the phase this task belongs to — the wave that writes the
1195
+ // specifications, or the wave that builds them. Absent when the graph does not
1196
+ // record it, which is different from the task belonging to neither.
1197
+ layer: methodLayerSchema.optional(),
1198
+ // When it was picked up, and when it settled. A reader computes how long it
1199
+ // took; neither this row nor this payload carries a duration.
1200
+ startedAt: utcIso8601.optional(),
1201
+ settledAt: utcIso8601.optional(),
1202
+ // The parts of this project's system the task builds, by their ids. Absent when
1203
+ // it builds none; never an empty list.
1204
+ builds: z8.array(systemPartIdSchema).min(1).max(SYSTEM_PARTS_MAX).optional(),
1205
+ // In one line, why the task built a part the phase's plan does not include, in
1206
+ // the words of whoever holds it. Absent when it gave no reason.
1207
+ whyOutsidePlan: descriptionText(BUILD_TASK_WHY_OUTSIDE_PLAN_MAX).optional(),
1208
+ // How long the task is expected to take, in whole minutes, from one minute to one
1209
+ // week. A plan, not a measurement; absent when no budget was set.
1210
+ budgetMinutes: z8.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
1211
+ // What kind of work the task is: building, checking other work, writing what to
1212
+ // build, or a person trying the product. Absent when the graph does not say.
1213
+ kind: buildTaskKindSchema.optional(),
1214
+ // One line from whoever holds the task about where it stands. Absent when there
1215
+ // is nothing to say.
1216
+ note: descriptionText(BUILD_TASK_NOTE_MAX).optional(),
1217
+ // On a task held for a person, the one thing they do or run to clear it, in one
1218
+ // line. Absent when the task names none.
1219
+ clearCommand: descriptionText(BUILD_TASK_CLEAR_COMMAND_MAX).optional(),
1220
+ // On a task held for a person, the crew member ready to take it on once it is
1221
+ // cleared, by callsign. Absent when nobody is standing by.
1222
+ standingBy: agentNameSchema.optional()
1223
+ }).strict().superRefine((task, ctx) => {
1224
+ if (task.reviews !== void 0 && task.reviews === task.id) {
1225
+ ctx.addIssue({
1226
+ code: z8.ZodIssueCode.custom,
1227
+ path: ["reviews"],
1228
+ message: `'${task.id}' reviews itself \u2014 a review names the OTHER task it checks.`
1229
+ });
1230
+ }
1231
+ if (task.layer !== void 0 && !isPhaseScopedLayer(task.layer)) {
1232
+ ctx.addIssue({
1233
+ code: z8.ZodIssueCode.custom,
1234
+ path: ["layer"],
1235
+ message: `'${task.layer}' is not a wave a phase runs \u2014 expected one of ${PHASE_SCOPED_LAYERS.join(", ")}.`
1236
+ });
1237
+ }
1238
+ const profiled = task.agent !== void 0 && task.agent !== HUMAN_AGENT;
1239
+ for (const field of ["discipline", "tier"]) {
1240
+ if (profiled && task[field] === void 0) {
1241
+ ctx.addIssue({
1242
+ code: z8.ZodIssueCode.custom,
1243
+ path: [field],
1244
+ message: `'${field}' is required on a task an agent holds.`
1245
+ });
1246
+ }
1247
+ if (!profiled && task[field] !== void 0) {
1248
+ ctx.addIssue({
1249
+ code: z8.ZodIssueCode.custom,
1250
+ path: [field],
1251
+ message: task.agent === void 0 ? `'${field}' must be absent on a task nobody holds.` : `'${field}' must be absent on work a person holds.`
1252
+ });
1253
+ }
1254
+ }
1255
+ });
1256
+ var buildPhaseSchema = z8.string().min(1).max(128);
1257
+ var BUILD_PHASE_TITLE_MAX = 160;
1258
+ var buildPhaseTitleSchema = z8.string().min(1).max(BUILD_PHASE_TITLE_MAX);
1259
+ var buildTaskListSchema = z8.array(buildTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT).superRefine((tasks, ctx) => {
1260
+ const seen = /* @__PURE__ */ new Set();
1261
+ for (const [index, task] of tasks.entries()) {
1262
+ if (seen.has(task.id)) {
1263
+ ctx.addIssue({
1264
+ code: z8.ZodIssueCode.custom,
1265
+ path: [index, "id"],
1266
+ message: `duplicate task id '${task.id}' \u2014 one graph names each task once.`
1267
+ });
1268
+ }
1269
+ seen.add(task.id);
1270
+ }
1271
+ });
1272
+ var buildTaskMarkSchema = z8.object({
1273
+ // The task that was marked.
1274
+ task: buildTaskIdSchema,
1275
+ // When it was first marked.
1276
+ at: utcIso8601
1277
+ }).strict();
1278
+ var buildSnapshotSchema = z8.object({
1279
+ // Which phase this graph belongs to.
1280
+ phase: buildPhaseSchema,
1281
+ // That phase's name in words, when the project gives it one.
1282
+ phaseTitle: buildPhaseTitleSchema.optional(),
1283
+ // Every task on it. Never a partial graph and never a change since last time.
1284
+ tasks: buildTaskListSchema,
1285
+ // When this graph was last reported.
1286
+ reportedAt: utcIso8601,
1287
+ // When work on this phase was first reported with no approval of its scope in force.
1288
+ builtWithoutApproval: utcIso8601.optional(),
1289
+ // Each task that started on a part still waiting for approval, and when it was
1290
+ // first seen doing so. Absent when there is none.
1291
+ tasksBuiltWithoutApproval: z8.array(buildTaskMarkSchema).min(1).max(BUILD_SNAPSHOT_TASK_LIMIT).optional()
1292
+ }).strict();
1293
+ var buildPhaseEntrySchema = z8.object({
1294
+ // Which phase has a stored graph.
1295
+ phase: buildPhaseSchema,
1296
+ // That phase's name in words, when the project gave it one.
1297
+ phaseTitle: buildPhaseTitleSchema.optional(),
1298
+ // When that phase's graph was last reported.
1299
+ reportedAt: utcIso8601,
1300
+ // Present when work on this phase was reported with no approval of its scope in force.
1301
+ builtWithoutApproval: z8.literal(true).optional()
1302
+ }).strict();
1303
+ var buildSnapshotReportSchema = z8.object({
1304
+ // Which phase this graph belongs to.
1305
+ phase: buildPhaseSchema,
1306
+ // That phase's name in words, when the project gives it one.
1307
+ phaseTitle: buildPhaseTitleSchema.optional(),
1308
+ // The whole graph, every time. Reporting a change instead of the graph is the
1309
+ // one thing this route does not accept.
1310
+ tasks: buildTaskListSchema
1311
+ }).strict();
1312
+ var buildSnapshotReportResultSchema = z8.object({
1313
+ // What happened. One outcome today; a refusal travels as the shared wire error.
1314
+ outcome: z8.literal("recorded"),
1315
+ // Which phase the graph was recorded against.
1316
+ phase: buildPhaseSchema,
1317
+ // When it was recorded.
1318
+ reportedAt: utcIso8601
1319
+ }).strict();
1320
+
1321
+ // ../events/dist/phase-plan.js
1322
+ import { z as z9 } from "zod";
1323
+ var PHASE_PLAN_FORMAT = 1;
1324
+ var PHASE_PLAN_CHANGES_MAX = 60;
1325
+ var PHASE_PLAN_SUMMARY_MAX = 160;
1326
+ var SCOPE_NOTE_MAX = 500;
1327
+ var PHASE_PLAN_PHASE_MAX = buildPhaseSchema.maxLength ?? 0;
1328
+ var PHASE_PLAN_CHANGE_KINDS = ["add", "change", "remove"];
1329
+ var phasePlanChangeSchema = z9.enum(PHASE_PLAN_CHANGE_KINDS, {
1330
+ error: `must be one of ${PHASE_PLAN_CHANGE_KINDS.join(", ")}`
1331
+ });
1332
+ var phasePlanPhaseSchema = phaseIdentitySchema.refine((v) => !isValidPhaseIdentity(v) || buildPhaseSchema.safeParse(String(v)).success, { message: `must be at most ${PHASE_PLAN_PHASE_MAX} characters` });
1333
+ var phasePlanSummarySchema = descriptionText(PHASE_PLAN_SUMMARY_MAX);
1334
+ var phasePlanEntrySchema = z9.object({
1335
+ part: systemPartIdSchema,
1336
+ change: phasePlanChangeSchema,
1337
+ summary: phasePlanSummarySchema
1338
+ }).strict();
1339
+ var phasePlanSchema = z9.object({
1340
+ format: z9.literal(PHASE_PLAN_FORMAT, { error: `must be ${PHASE_PLAN_FORMAT}` }),
1341
+ phase: phasePlanPhaseSchema,
1342
+ changes: z9.array(phasePlanEntrySchema).min(1, { message: "must list at least one part" }).max(PHASE_PLAN_CHANGES_MAX, { message: `can list at most ${PHASE_PLAN_CHANGES_MAX} parts` })
1343
+ }).strict();
1344
+ var scopePhaseSchema = buildPhaseSchema.refine((v) => isValidPhaseIdentity(v), {
1345
+ message: "must be a phase name"
1346
+ });
1347
+ var SCOPE_STATE_NAMES = [
1348
+ "not-planned",
1349
+ "waiting",
1350
+ "sent-back",
1351
+ "approved",
1352
+ "partly-stale",
1353
+ "withdrawn"
1354
+ ];
1355
+ var scopeStateNameSchema = z9.enum(SCOPE_STATE_NAMES);
1356
+ var plannedScopeStateSchema = scopeStateNameSchema.exclude(["not-planned"]);
1357
+ var SCOPE_PART_STATUSES = ["approved", "waiting"];
1358
+ var scopePartStatusSchema = z9.enum(SCOPE_PART_STATUSES);
1359
+ var SCOPE_ACTS = ["approve", "send-back", "withdraw"];
1360
+ var scopeActSchema = z9.enum(SCOPE_ACTS);
1361
+ var SCOPE_GIVEN_IN = ["console", "chat"];
1362
+ var scopeGivenInSchema = z9.enum(SCOPE_GIVEN_IN);
1363
+ var scopeNoteSchema = descriptionText(SCOPE_NOTE_MAX);
1364
+ var scopeNoteWireSchema = z9.string().max(SCOPE_NOTE_MAX);
1365
+ var SCOPE_PART_VERDICTS = ["ok", "change"];
1366
+ var scopePartVerdictSchema = z9.enum(SCOPE_PART_VERDICTS);
1367
+ var scopeDecisionPartSchema = z9.object({
1368
+ part: systemPartIdSchema,
1369
+ verdict: scopePartVerdictSchema,
1370
+ note: scopeNoteWireSchema.optional()
1371
+ }).strict();
1372
+ var planVersionSchema = z9.number().int().min(1);
1373
+ var carriedPhaseSchema = z9.object({
1374
+ phase: scopePhaseSchema,
1375
+ number: z9.number().int().min(1)
1376
+ }).strict();
1377
+ var carriedFromEntrySchema = carriedPhaseSchema.extend({
1378
+ part: systemPartIdSchema
1379
+ }).strict();
1380
+ var phasePlanRequestSchema = z9.object({
1381
+ descriptionVersion: z9.number().int().min(1),
1382
+ plan: z9.unknown()
1383
+ }).strict();
1384
+ var phasePlanRecordedSchema = z9.object({
1385
+ outcome: z9.literal("recorded"),
1386
+ version: planVersionSchema,
1387
+ phaseNumber: z9.number().int().min(1),
1388
+ state: plannedScopeStateSchema,
1389
+ changedParts: z9.array(systemPartIdSchema).max(PHASE_PLAN_CHANGES_MAX),
1390
+ typedApprovalOffered: z9.boolean(),
1391
+ reportedAt: utcIso8601,
1392
+ carriedFrom: z9.array(carriedFromEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX).optional()
1393
+ }).strict();
1394
+ var phasePlanRefusedSchema = z9.object({
1395
+ outcome: z9.literal("refused"),
1396
+ problems: z9.array(systemDescriptionProblemSchema).min(1)
1397
+ }).strict();
1398
+ var phasePlanUnavailableSchema = z9.object({
1399
+ outcome: z9.literal("unavailable")
1400
+ }).strict();
1401
+ var REMOVAL_OWED_ACTS = ["dropped", "removed"];
1402
+ var removalOwedPartSchema = z9.object({
1403
+ part: systemPartIdSchema,
1404
+ name: systemPartSchema.shape.name,
1405
+ phase: scopePhaseSchema,
1406
+ number: z9.number().int().min(1),
1407
+ act: z9.enum(REMOVAL_OWED_ACTS)
1408
+ }).strict();
1409
+ var phasePlanRemovalOwedSchema = z9.object({
1410
+ outcome: z9.literal("removal-owed"),
1411
+ parts: z9.array(removalOwedPartSchema).min(1).max(SYSTEM_PARTS_MAX)
1412
+ }).strict();
1413
+ var phasePlanResultSchema = z9.discriminatedUnion("outcome", [
1414
+ phasePlanRecordedSchema,
1415
+ phasePlanRefusedSchema,
1416
+ phasePlanUnavailableSchema,
1417
+ phasePlanRemovalOwedSchema
1418
+ ]);
1419
+ var scopeApproveRequestSchema = z9.object({
1420
+ act: z9.literal("approve"),
1421
+ planVersion: planVersionSchema,
1422
+ parts: z9.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX)
1423
+ }).strict();
1424
+ var scopeSendBackRequestSchema = z9.object({
1425
+ act: z9.literal("send-back"),
1426
+ planVersion: planVersionSchema,
1427
+ parts: z9.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
1428
+ note: scopeNoteWireSchema.optional()
1429
+ }).strict();
1430
+ var scopeWithdrawRequestSchema = z9.object({
1431
+ act: z9.literal("withdraw"),
1432
+ planVersion: planVersionSchema
1433
+ }).strict();
1434
+ var scopeDecisionRequestSchema = z9.discriminatedUnion("act", [
1435
+ scopeApproveRequestSchema,
1436
+ scopeSendBackRequestSchema,
1437
+ scopeWithdrawRequestSchema
1438
+ ]);
1439
+ var scopeDecisionRecordedSchema = z9.object({
1440
+ outcome: z9.literal("recorded"),
1441
+ act: scopeActSchema,
1442
+ phase: scopePhaseSchema,
1443
+ planVersion: planVersionSchema,
1444
+ state: plannedScopeStateSchema,
1445
+ decidedAt: utcIso8601
1446
+ }).strict();
1447
+ var scopeDecisionAlreadyApprovedSchema = z9.object({
1448
+ outcome: z9.literal("already-approved"),
1449
+ replanNeeded: z9.boolean(),
1450
+ reason: z9.string().min(1)
1451
+ }).strict();
1452
+ var scopeDecisionRefusedSchema = z9.object({
1453
+ outcome: z9.literal("refused"),
1454
+ problems: z9.array(systemDescriptionProblemSchema).min(1)
1455
+ }).strict();
1456
+ var scopeDecisionUnavailableSchema = z9.object({
1457
+ outcome: z9.literal("unavailable")
1458
+ }).strict();
1459
+ function reasonOutcome(outcome) {
1460
+ return z9.object({ outcome: z9.literal(outcome), reason: z9.string().min(1) }).strict();
1461
+ }
1462
+ var scopeDecisionResultSchema = z9.discriminatedUnion("outcome", [
1463
+ scopeDecisionRecordedSchema,
1464
+ scopeDecisionAlreadyApprovedSchema,
1465
+ scopeDecisionRefusedSchema,
1466
+ scopeDecisionUnavailableSchema,
1467
+ reasonOutcome("not-owner"),
1468
+ reasonOutcome("no-owner"),
1469
+ reasonOutcome("plan-changed"),
1470
+ reasonOutcome("nothing-to-withdraw"),
1471
+ reasonOutcome("build-started")
1472
+ ]);
1473
+ var scopeReadPartSchema = z9.object({
1474
+ part: systemPartIdSchema,
1475
+ name: systemPartSchema.shape.name,
1476
+ change: phasePlanChangeSchema,
1477
+ summary: phasePlanSummarySchema,
1478
+ status: scopePartStatusSchema,
1479
+ changed: z9.boolean(),
1480
+ carriedFrom: carriedPhaseSchema.optional()
1481
+ }).strict();
1482
+ var scopeReadDecisionSchema = z9.object({
1483
+ act: scopeActSchema,
1484
+ planVersion: planVersionSchema,
1485
+ givenIn: scopeGivenInSchema,
1486
+ at: utcIso8601,
1487
+ parts: z9.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
1488
+ note: scopeNoteWireSchema.optional()
1489
+ }).strict();
1490
+ var scopeReadNotPlannedSchema = z9.object({
1491
+ phase: scopePhaseSchema,
1492
+ state: z9.literal("not-planned"),
1493
+ canDecide: z9.boolean()
1494
+ }).strict();
1495
+ var scopeReadPlannedSchema = z9.object({
1496
+ phase: scopePhaseSchema,
1497
+ state: plannedScopeStateSchema,
1498
+ phaseNumber: z9.number().int().min(1),
1499
+ planVersion: planVersionSchema,
1500
+ parts: z9.array(scopeReadPartSchema).min(1).max(PHASE_PLAN_CHANGES_MAX),
1501
+ decisions: z9.array(scopeReadDecisionSchema),
1502
+ canDecide: z9.boolean(),
1503
+ replanLine: z9.string().min(1).optional()
1504
+ }).strict();
1505
+ var scopeReadSchema = z9.union([scopeReadNotPlannedSchema, scopeReadPlannedSchema]);
1506
+
1507
+ // ../events/dist/build-record-v3.js
1508
+ import { z as z14 } from "zod";
1509
+
1510
+ // ../events/dist/board-vocabulary.js
1511
+ import { z as z10 } from "zod";
1512
+ var boardStepStateSchema = z10.enum([
1513
+ "not-instrumented",
1514
+ "done",
1515
+ "did-not-pass",
1516
+ "could-not-complete",
1517
+ "not-yet-available",
1518
+ "qualified"
1519
+ ]);
1520
+ var boardGateNameSchema = z10.enum([
1521
+ "scope",
1522
+ "architecture",
1523
+ "specs",
1524
+ "taskgraph",
1525
+ "phase-close"
1526
+ ]);
1527
+
1528
+ // ../events/dist/code-link.js
1529
+ import { z as z11 } from "zod";
1530
+ var codeLinkAddressSchema = z11.string().regex(GITHUB_REPOSITORY_ADDRESS, { message: "must be a GitHub repository address: https://github.com/<owner>/<repo>" }).refine((v) => !looksLikeSecret(v), { message: "looks like a password or key, so it is not sent" });
1531
+ var codeLinkFactsSchema = z11.object({
1532
+ repositoryAddress: codeLinkAddressSchema.nullable(),
1533
+ headOnGitHub: z11.boolean()
1534
+ }).strict().refine((v) => !(v.headOnGitHub && v.repositoryAddress === null), {
1535
+ message: "cannot be true when the code is not on GitHub",
1536
+ path: ["headOnGitHub"]
1537
+ });
1538
+ var PART_CODE_LINK_SCOPES = ["part", "phase"];
1539
+ var PART_CODE_LINK_RANGE_REASONS = ["not-github", "not-pushed"];
1540
+ var PART_CODE_LINK_URL_PREFIX = "https://github.com/";
1541
+ var partCodeLinkGithubSchema = z11.object({
1542
+ kind: z11.literal("github"),
1543
+ scope: z11.enum(PART_CODE_LINK_SCOPES),
1544
+ url: z11.string().startsWith(PART_CODE_LINK_URL_PREFIX, { message: "must start with https://github.com/" })
1545
+ }).strict();
1546
+ var partCodeLinkRangeSchema = z11.object({
1547
+ kind: z11.literal("range"),
1548
+ why: z11.enum(PART_CODE_LINK_RANGE_REASONS),
1549
+ from: closeCommitSchema,
1550
+ to: closeCommitSchema
1551
+ }).strict();
1552
+ var partCodeLinkNoneSchema = z11.object({
1553
+ kind: z11.literal("none")
1554
+ }).strict();
1555
+ var partCodeLinkSchema = z11.discriminatedUnion("kind", [
1556
+ partCodeLinkGithubSchema,
1557
+ partCodeLinkRangeSchema,
1558
+ partCodeLinkNoneSchema
1559
+ ]);
1560
+
1561
+ // ../events/dist/observed-changes.js
1562
+ import { z as z12 } from "zod";
1563
+ var OBSERVED_PATHS_LISTED_MAX = 50;
1564
+ var OBSERVED_PATH_MAX = 300;
1565
+ var OBSERVED_COUNT_MAX = 1e6;
1566
+ var observedCountSchema = z12.number({ error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).int({ error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).min(0, { error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` }).max(OBSERVED_COUNT_MAX, { error: `must be a whole number from 0 to ${OBSERVED_COUNT_MAX}` });
1567
+ var observedDescriptionVersionSchema = z12.number().int().min(1);
1568
+ var observedPathSchema = z12.string().min(1, { message: "must not be empty" }).max(OBSERVED_PATH_MAX, { message: `must be at most ${OBSERVED_PATH_MAX} characters` });
1569
+ var observedPathListSchema = z12.array(observedPathSchema).max(OBSERVED_PATHS_LISTED_MAX, { message: `can list at most ${OBSERVED_PATHS_LISTED_MAX} paths` }).refine((paths) => new Set(paths).size === paths.length, { message: "must name each path once" });
1570
+ var observedMethodFilesSchema = z12.object({
1571
+ added: observedCountSchema,
1572
+ changed: observedCountSchema,
1573
+ removed: observedCountSchema
1574
+ }).strict();
1575
+ var observedFilesSchema = z12.object({
1576
+ added: observedCountSchema,
1577
+ changed: observedCountSchema,
1578
+ removed: observedCountSchema,
1579
+ testFilesAdded: observedCountSchema
1580
+ }).strict().refine((v) => v.testFilesAdded <= v.added, {
1581
+ message: "cannot be more than the files added",
1582
+ path: ["testFilesAdded"]
1583
+ });
1584
+ function filesChanged(files) {
1585
+ return files.added + files.changed + files.removed;
1586
+ }
1587
+ function pathsFitTheCount(files, paths) {
1588
+ return files === null ? paths.length === 0 : paths.length <= filesChanged(files);
1589
+ }
1590
+ function checkPartsUnique(parts, ctx, at) {
1591
+ const seen = /* @__PURE__ */ new Set();
1592
+ parts.forEach((entry, i) => {
1593
+ if (seen.has(entry.part)) {
1594
+ ctx.addIssue({ code: "custom", path: [...at, i, "part"], message: `names the part "${entry.part}" twice` });
1595
+ }
1596
+ seen.add(entry.part);
1597
+ });
1598
+ }
1599
+ var observedBasisCountSchema = z12.object({
1600
+ outcome: z12.literal("count"),
1601
+ phase: phaseNameSchema,
1602
+ startCommit: closeCommitSchema,
1603
+ descriptionVersion: observedDescriptionVersionSchema,
1604
+ description: systemDescriptionSchema
1605
+ }).strict();
1606
+ var observedBasisCannotCountSchema = z12.object({
1607
+ outcome: z12.literal("cannot-count"),
1608
+ phase: phaseNameSchema,
1609
+ reason: z12.literal("no-start-commit")
1610
+ }).strict();
1611
+ var observedBasisNothingToCountSchema = z12.object({
1612
+ outcome: z12.literal("nothing-to-count"),
1613
+ reason: z12.enum(["no-open-phase", "no-description"])
1614
+ }).strict();
1615
+ var observedBasisSchema = z12.discriminatedUnion("outcome", [
1616
+ observedBasisCountSchema,
1617
+ observedBasisCannotCountSchema,
1618
+ observedBasisNothingToCountSchema
1619
+ ]);
1620
+ var observedReportPartSchema = z12.object({
1621
+ part: systemPartIdSchema,
1622
+ added: observedCountSchema,
1623
+ changed: observedCountSchema,
1624
+ removed: observedCountSchema,
1625
+ testFilesAdded: observedCountSchema
1626
+ }).strict().refine((v) => v.testFilesAdded <= v.added, {
1627
+ message: "cannot be more than the files added",
1628
+ path: ["testFilesAdded"]
1629
+ }).refine((v) => filesChanged(v) >= 1, {
1630
+ message: "a part is listed only when at least one file changed under it",
1631
+ path: ["added"]
1632
+ });
1633
+ var observedReportOutsideSchema = z12.object({
1634
+ added: observedCountSchema,
1635
+ changed: observedCountSchema,
1636
+ removed: observedCountSchema,
1637
+ testFilesAdded: observedCountSchema,
1638
+ paths: observedPathListSchema
1639
+ }).strict().refine((v) => v.testFilesAdded <= v.added, {
1640
+ message: "cannot be more than the files added",
1641
+ path: ["testFilesAdded"]
1642
+ }).refine((v) => pathsFitTheCount(v, v.paths), {
1643
+ message: "lists more paths than files changed",
1644
+ path: ["paths"]
1645
+ });
1646
+ var observedChangesReportSchema = z12.object({
1647
+ phase: phaseNameSchema,
1648
+ startCommit: closeCommitSchema,
1649
+ headCommit: closeCommitSchema,
1650
+ headCommittedAt: utcIso8601,
1651
+ commits: observedCountSchema,
1652
+ descriptionVersion: observedDescriptionVersionSchema,
1653
+ parts: z12.array(observedReportPartSchema).max(SYSTEM_PARTS_MAX, { message: `can list at most ${SYSTEM_PARTS_MAX} parts` }),
1654
+ methodFiles: observedMethodFilesSchema,
1655
+ outsideEveryPart: observedReportOutsideSchema,
1656
+ codeLink: codeLinkFactsSchema
1657
+ }).strict().superRefine((v, ctx) => checkPartsUnique(v.parts, ctx, ["parts"]));
1658
+ var observedSaidSchema = z12.object({
1659
+ title: buildTaskSchema.shape.title,
1660
+ agent: buildTaskSchema.shape.agent,
1661
+ whyOutsidePlan: buildTaskSchema.shape.whyOutsidePlan
1662
+ }).strict();
1663
+ var observedNotInPlanSchema = z12.object({
1664
+ part: systemPartIdSchema,
1665
+ name: systemPartSchema.shape.name,
1666
+ said: z12.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT)
1667
+ }).strict();
1668
+ var observedChangesRecordedSchema = z12.object({
1669
+ outcome: z12.literal("recorded"),
1670
+ notInPlan: z12.array(observedNotInPlanSchema).max(SYSTEM_PARTS_MAX).superRefine((parts, ctx) => checkPartsUnique(parts, ctx, [])),
1671
+ outside: z12.object({
1672
+ files: observedCountSchema,
1673
+ example: observedPathSchema.nullable()
1674
+ }).strict().refine((v) => v.example === null || v.files >= 1, {
1675
+ message: "names an example when no file changed",
1676
+ path: ["example"]
1677
+ })
1678
+ }).strict();
1679
+ var observedChangesKeptSchema = z12.object({
1680
+ outcome: z12.literal("kept")
1681
+ }).strict();
1682
+ var observedChangesReportResultSchema = z12.discriminatedUnion("outcome", [
1683
+ observedChangesRecordedSchema,
1684
+ observedChangesKeptSchema
1685
+ ]);
1686
+ var OBSERVE_NOT_SENT_REASONS = [
1687
+ "not-configured",
1688
+ "nothing-to-count",
1689
+ "cannot-count",
1690
+ "start-not-here",
1691
+ "start-not-on-branch",
1692
+ "basis-moved",
1693
+ "refused",
1694
+ "unreachable"
1695
+ ];
1696
+ var observeRunRecordedSchema = z12.object({ outcome: z12.literal("recorded") }).strict();
1697
+ var observeRunKeptSchema = z12.object({ outcome: z12.literal("kept") }).strict();
1698
+ var observeRunNotSentSchema = z12.object({
1699
+ outcome: z12.literal("not-sent"),
1700
+ reason: z12.enum(OBSERVE_NOT_SENT_REASONS)
1701
+ }).strict();
1702
+ var observeRunResultSchema = z12.discriminatedUnion("outcome", [
1703
+ observeRunRecordedSchema,
1704
+ observeRunKeptSchema,
1705
+ observeRunNotSentSchema
1706
+ ]);
1707
+ var observedChecksFiredSchema = z12.array(z12.string().min(1)).refine((names) => names.every((name, i) => i === 0 || names[i - 1] < name), {
1708
+ message: "must be sorted, each name once"
1709
+ });
1710
+ var observedCountingSchema = z12.discriminatedUnion("state", [
1711
+ z12.object({
1712
+ state: z12.literal("counted"),
1713
+ startCommit: closeCommitSchema,
1714
+ startRecordedAt: utcIso8601,
1715
+ headCommit: closeCommitSchema,
1716
+ headCommittedAt: utcIso8601,
1717
+ commits: observedCountSchema,
1718
+ countedAt: utcIso8601
1719
+ }).strict(),
1720
+ z12.object({
1721
+ state: z12.literal("not-counted-yet"),
1722
+ startCommit: closeCommitSchema,
1723
+ startRecordedAt: utcIso8601
1724
+ }).strict(),
1725
+ z12.object({
1726
+ state: z12.literal("cannot-count")
1727
+ }).strict()
1728
+ ]);
1729
+ var observedPartSchema = z12.object({
1730
+ part: systemPartIdSchema,
1731
+ files: observedFilesSchema.nullable(),
1732
+ checksFired: observedChecksFiredSchema
1733
+ }).strict().refine((v) => v.files !== null && filesChanged(v.files) >= 1 || v.checksFired.length >= 1, {
1734
+ message: "a part is listed only when files changed under it or a check fired on one",
1735
+ path: ["files"]
1736
+ });
1737
+ var observedOutsideSchema = z12.object({
1738
+ files: observedFilesSchema.nullable(),
1739
+ paths: observedPathListSchema,
1740
+ checksFired: observedChecksFiredSchema
1741
+ }).strict().refine((v) => pathsFitTheCount(v.files, v.paths), {
1742
+ message: "lists more paths than files changed",
1743
+ path: ["paths"]
1744
+ });
1745
+ var observedChangesSchema = z12.object({
1746
+ phase: phaseNameSchema,
1747
+ counting: observedCountingSchema,
1748
+ descriptionVersion: observedDescriptionVersionSchema.nullable(),
1749
+ parts: z12.array(observedPartSchema).max(SYSTEM_PARTS_MAX),
1750
+ methodFiles: observedMethodFilesSchema.nullable(),
1751
+ outsideEveryPart: observedOutsideSchema,
1752
+ codeLink: codeLinkFactsSchema.nullable()
1753
+ }).strict().superRefine((v, ctx) => {
1754
+ checkPartsUnique(v.parts, ctx, ["parts"]);
1755
+ const counted2 = v.counting.state === "counted";
1756
+ const rule = counted2 ? "must be present when the phase is counted" : "must be null until the phase is counted";
1757
+ if (v.methodFiles !== null !== counted2)
1758
+ ctx.addIssue({ code: "custom", path: ["methodFiles"], message: rule });
1759
+ if (v.outsideEveryPart.files !== null !== counted2) {
1760
+ ctx.addIssue({ code: "custom", path: ["outsideEveryPart", "files"], message: rule });
1761
+ }
1762
+ if (v.codeLink !== null !== counted2)
1763
+ ctx.addIssue({ code: "custom", path: ["codeLink"], message: rule });
1764
+ v.parts.forEach((entry, i) => {
1765
+ if (entry.files !== null !== counted2)
1766
+ ctx.addIssue({ code: "custom", path: ["parts", i, "files"], message: rule });
1767
+ });
1768
+ if (counted2 && v.descriptionVersion === null) {
1769
+ ctx.addIssue({ code: "custom", path: ["descriptionVersion"], message: "must name the version the counts used" });
1770
+ }
1771
+ });
1772
+ var partOutsideThePlanSchema = z12.object({
1773
+ part: systemPartIdSchema,
1774
+ name: systemPartSchema.shape.name,
1775
+ seen: observedFilesSchema.nullable(),
1776
+ tasks: z12.array(observedSaidSchema.extend({ id: buildTaskSchema.shape.id }).strict()).max(BUILD_SNAPSHOT_TASK_LIMIT)
1777
+ }).strict();
1778
+ function shortCommit(commit) {
1779
+ return commit.slice(0, 7);
1780
+ }
1781
+
1782
+ // ../events/dist/phase-review.js
1783
+ import { z as z13 } from "zod";
1784
+ var BUILT_PART_ACTS = Object.freeze(["accept", "question", "send-back"]);
1785
+ var NOT_BUILT_PART_ACTS = Object.freeze(["carry", "drop"]);
1786
+ var REVIEW_ACTS = Object.freeze([...BUILT_PART_ACTS, ...NOT_BUILT_PART_ACTS]);
1787
+ var reviewActSchema = z13.enum(REVIEW_ACTS);
1788
+ function reviewActsFor(built) {
1789
+ return built ? BUILT_PART_ACTS : NOT_BUILT_PART_ACTS;
1790
+ }
1791
+ var REVIEW_NOTE_MAX = SCOPE_NOTE_MAX;
1792
+ var reviewNoteWireSchema = z13.string().max(REVIEW_NOTE_MAX);
1793
+ var reviewNoteSchema = z13.string().min(1).max(REVIEW_NOTE_MAX);
1794
+ var REVIEW_ANSWER_MAX = 1e3;
1795
+ var reviewAgentAnswerSchema = z13.string().min(1).max(REVIEW_ANSWER_MAX);
1796
+ var REVIEW_PARTS_MAX = PHASE_PLAN_CHANGES_MAX * 2;
1797
+ var REVIEW_PART_CHANGES = Object.freeze([...PHASE_PLAN_CHANGE_KINDS, "not-in-plan"]);
1798
+ var reviewPartChangeSchema = z13.enum(REVIEW_PART_CHANGES);
1799
+ var REVIEW_DECISIONS = Object.freeze(["accepted", "carried", "dropped"]);
1800
+ var reviewDecisionSchema = z13.enum(REVIEW_DECISIONS);
1801
+ function actOf(decision) {
1802
+ if (decision === "accepted")
1803
+ return "accept";
1804
+ if (decision === "carried")
1805
+ return "carry";
1806
+ return "drop";
1807
+ }
1808
+ var reviewCarriedToSchema = carriedPhaseSchema.nullable();
1809
+ var reviewStepNameSchema = stepNameSchema.refine((v) => !stepIdSchema.safeParse(v.trim()).success, {
1810
+ message: "must be the step's plain name"
1811
+ });
1812
+ var reviewStepSchema = z13.object({
1813
+ name: reviewStepNameSchema.nullable(),
1814
+ state: unfinishedStepSchema.shape.state,
1815
+ judged: z13.boolean()
1816
+ }).strict();
1817
+ function codeLinkLine(link) {
1818
+ switch (link.kind) {
1819
+ case "github":
1820
+ return link.scope === "part" ? `The code: ${link.url}` : `The code: ${link.url} GitHub cannot show only this part's code, so this opens every change in this phase.`;
1821
+ case "range":
1822
+ return link.why === "not-github" ? `The code is on your machine, not on GitHub. This phase's changes are ${shortCommit(link.from)}..${shortCommit(link.to)}.` : `This phase's newest changes are not on GitHub yet. They are ${shortCommit(link.from)}..${shortCommit(link.to)} on your machine.`;
1823
+ case "none":
1824
+ return void 0;
1825
+ default:
1826
+ return assertNever(link);
1827
+ }
1828
+ }
1829
+ function assertNever(value) {
1830
+ throw new Error(`unexpected value: ${JSON.stringify(value)}`);
1831
+ }
1832
+ var openReviewRequestSchema = z13.object({
1833
+ actor: closeActorSchema,
1834
+ acceptanceOutcome: acceptanceOutcomeSchema,
1835
+ statements: closeStatementsSchema
1836
+ }).strict();
1837
+ var REVIEW_OPEN_TASKS_SHOWN_MAX = 10;
1838
+ var reviewOpenedSchema = z13.object({
1839
+ outcome: z13.literal("opened"),
1840
+ round: z13.number().int().min(1),
1841
+ parts: z13.number().int().min(0).max(REVIEW_PARTS_MAX),
1842
+ notBuilt: z13.number().int().min(0).max(REVIEW_PARTS_MAX),
1843
+ counted: z13.boolean(),
1844
+ typedReviewOffered: z13.boolean()
1845
+ }).strict().refine((v) => v.notBuilt <= v.parts, { message: "cannot exceed the parts in the round", path: ["notBuilt"] });
1846
+ var reviewTasksOpenSchema = z13.object({
1847
+ outcome: z13.literal("tasks-open"),
1848
+ open: z13.array(buildTaskSchema.shape.title).min(1).max(REVIEW_OPEN_TASKS_SHOWN_MAX),
1849
+ more: z13.number().int().min(0).max(BUILD_SNAPSHOT_TASK_LIMIT)
1850
+ }).strict();
1851
+ var reviewOutstandingSchema = z13.object({
1852
+ outcome: z13.literal("outstanding"),
1853
+ steps: z13.array(reviewStepSchema).min(1)
1854
+ }).strict();
1855
+ var reviewCleanOverFindingsSchema = z13.object({
1856
+ outcome: z13.literal("clean-over-findings"),
1857
+ findings: z13.number().int().min(1)
1858
+ }).strict();
1859
+ var reviewRefusedSchema = z13.object({
1860
+ outcome: z13.literal("refused"),
1861
+ problems: z13.array(systemDescriptionProblemSchema).min(1)
1862
+ }).strict();
1863
+ function bareReviewOutcome(outcome) {
1864
+ return z13.object({ outcome: z13.literal(outcome) }).strict();
1865
+ }
1866
+ var openReviewResultSchema = z13.discriminatedUnion("outcome", [
1867
+ reviewOpenedSchema,
1868
+ reviewTasksOpenSchema,
1869
+ bareReviewOutcome("no-build"),
1870
+ bareReviewOutcome("review-open"),
1871
+ reviewOutstandingSchema,
1872
+ reviewCleanOverFindingsSchema,
1873
+ reviewRefusedSchema,
1874
+ bareReviewOutcome("unavailable")
1875
+ ]);
1876
+ var reviewPhaseNotOpenSchema = z13.object({
1877
+ statusCode: z13.literal(409),
1878
+ error: z13.literal("PhaseNotOpen"),
1879
+ message: z13.string().min(1),
1880
+ openPhase: z13.string().nullable()
1881
+ }).strict();
1882
+ var openReviewAnswerSchema = z13.union([openReviewResultSchema, reviewPhaseNotOpenSchema]);
1883
+ var reviewDecisionEntrySchema = z13.object({
1884
+ part: systemPartIdSchema,
1885
+ act: reviewActSchema,
1886
+ note: reviewNoteWireSchema.optional()
1887
+ }).strict();
1888
+ var reviewDecisionRequestSchema = z13.object({
1889
+ reviewVersion: z13.number().int().min(0),
1890
+ decisions: z13.array(reviewDecisionEntrySchema).max(REVIEW_PARTS_MAX)
1891
+ }).strict();
1892
+ var reviewDecidedSchema = z13.object({
1893
+ part: systemPartIdSchema,
1894
+ name: systemPartSchema.shape.name,
1895
+ act: reviewActSchema,
1896
+ carriedTo: reviewCarriedToSchema.optional()
1897
+ }).strict().refine((v) => v.carriedTo !== void 0 === (v.act === "carry"), {
1898
+ message: "is present exactly on a carry",
1899
+ path: ["carriedTo"]
1900
+ });
1901
+ var reviewRecordedSchema = z13.object({
1902
+ outcome: z13.literal("recorded"),
1903
+ decided: z13.array(reviewDecidedSchema).max(REVIEW_PARTS_MAX),
1904
+ waiting: z13.number().int().min(0).max(REVIEW_PARTS_MAX),
1905
+ closed: z13.boolean(),
1906
+ reviewVersion: z13.number().int().min(1)
1907
+ }).strict().superRefine((v, ctx) => {
1908
+ if (v.closed && v.waiting !== 0) {
1909
+ ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be 0 once the phase is closed" });
1910
+ }
1911
+ if (v.decided.length === 0 && !v.closed) {
1912
+ ctx.addIssue({ code: "custom", path: ["decided"], message: "may be empty only when the phase closed" });
1913
+ }
1914
+ });
1915
+ var REVIEW_NOT_DECIDABLE_WHY = Object.freeze(["settled", "question-waiting", "sent-back"]);
1916
+ var reviewNotDecidablePartSchema = z13.object({
1917
+ part: systemPartIdSchema,
1918
+ name: systemPartSchema.shape.name,
1919
+ why: z13.enum(REVIEW_NOT_DECIDABLE_WHY)
1920
+ }).strict();
1921
+ var reviewNotDecidableSchema = z13.object({
1922
+ outcome: z13.literal("not-decidable"),
1923
+ parts: z13.array(reviewNotDecidablePartSchema).min(1).max(REVIEW_PARTS_MAX)
1924
+ }).strict();
1925
+ var reviewCloseRefusedSchema = z13.object({
1926
+ outcome: z13.literal("close-refused"),
1927
+ steps: z13.array(reviewStepSchema)
1928
+ }).strict();
1929
+ var reviewWalkChangedSchema = z13.object({
1930
+ outcome: z13.literal("walk-changed"),
1931
+ findings: z13.number().int().min(1)
1932
+ }).strict();
1933
+ var reviewDecisionResultSchema = z13.discriminatedUnion("outcome", [
1934
+ reviewRecordedSchema,
1935
+ bareReviewOutcome("review-changed"),
1936
+ bareReviewOutcome("no-review"),
1937
+ reviewNotDecidableSchema,
1938
+ reviewCloseRefusedSchema,
1939
+ reviewWalkChangedSchema,
1940
+ bareReviewOutcome("not-owner"),
1941
+ bareReviewOutcome("no-owner"),
1942
+ reviewRefusedSchema,
1943
+ bareReviewOutcome("unavailable")
1944
+ ]);
1945
+ var reviewSentBackSchema = z13.object({
1946
+ round: z13.number().int().min(1),
1947
+ note: reviewNoteSchema
1948
+ }).strict();
1949
+ var reviewQuestionSchema = z13.object({
1950
+ question: reviewNoteSchema,
1951
+ agentAnswer: reviewAgentAnswerSchema.optional()
1952
+ }).strict();
1953
+ var reviewTaskSchema = z13.object({
1954
+ title: buildTaskSchema.shape.title,
1955
+ state: buildTaskStateSchema
1956
+ }).strict();
1957
+ var reviewViewPartSchema = z13.object({
1958
+ part: systemPartIdSchema,
1959
+ name: systemPartSchema.shape.name,
1960
+ change: reviewPartChangeSchema,
1961
+ built: z13.boolean(),
1962
+ builtWithoutApproval: z13.boolean(),
1963
+ decidable: z13.boolean(),
1964
+ waiting: z13.boolean(),
1965
+ decision: reviewDecisionSchema.optional(),
1966
+ reason: reviewNoteSchema.optional(),
1967
+ carriedTo: reviewCarriedToSchema.optional(),
1968
+ sentBack: z13.array(reviewSentBackSchema),
1969
+ questions: z13.array(reviewQuestionSchema),
1970
+ tasks: z13.array(reviewTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
1971
+ said: z13.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
1972
+ codeLink: partCodeLinkSchema
1973
+ }).strict().superRefine((v, ctx) => {
1974
+ if (v.waiting !== (v.decision === void 0)) {
1975
+ ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be true exactly when the part has no decision" });
1976
+ }
1977
+ if (v.decidable && !v.waiting) {
1978
+ ctx.addIssue({ code: "custom", path: ["decidable"], message: "cannot be true for a settled part" });
1979
+ }
1980
+ if (v.decision !== void 0 && !reviewActsFor(v.built).includes(actOf(v.decision))) {
1981
+ ctx.addIssue({ code: "custom", path: ["decision"], message: "is not an act this part takes" });
1982
+ }
1983
+ const carriedOrDropped = v.decision === "carried" || v.decision === "dropped";
1984
+ if (v.reason !== void 0 !== carriedOrDropped) {
1985
+ ctx.addIssue({ code: "custom", path: ["reason"], message: "is present exactly on a carry or a drop" });
1986
+ }
1987
+ if (v.carriedTo !== void 0 !== (v.decision === "carried")) {
1988
+ ctx.addIssue({ code: "custom", path: ["carriedTo"], message: "is present exactly on a carry" });
1989
+ }
1990
+ if (v.change === "not-in-plan" && !v.built) {
1991
+ ctx.addIssue({ code: "custom", path: ["built"], message: "is always true for a part outside the plan" });
1992
+ }
1993
+ });
1994
+ var reviewPhaseNumberSchema = z13.number().int().min(1).nullable();
1995
+ var phaseReviewNoneSchema = z13.object({
1996
+ state: z13.literal("none"),
1997
+ phase: scopePhaseSchema,
1998
+ number: reviewPhaseNumberSchema,
1999
+ closedWithoutReview: z13.boolean()
2000
+ }).strict();
2001
+ var reviewWalkSchema = z13.object({
2002
+ verdict: walkVerdictSchema,
2003
+ findings: z13.number().int().min(0)
2004
+ }).strict();
2005
+ function reviewedViewSchema(state) {
2006
+ return z13.object({
2007
+ state: z13.literal(state),
2008
+ phase: scopePhaseSchema,
2009
+ number: reviewPhaseNumberSchema,
2010
+ cycle: z13.number().int().min(0),
2011
+ round: z13.number().int().min(1),
2012
+ reviewVersion: z13.number().int().min(1),
2013
+ typedReviewOffered: z13.boolean(),
2014
+ counted: z13.boolean(),
2015
+ walk: reviewWalkSchema,
2016
+ parts: z13.array(reviewViewPartSchema).max(REVIEW_PARTS_MAX),
2017
+ observed: observedChangesSchema.nullable(),
2018
+ description: systemDescriptionSchema
2019
+ }).strict().superRefine((v, ctx) => {
2020
+ const seen = /* @__PURE__ */ new Set();
2021
+ v.parts.forEach((p, i) => {
2022
+ if (seen.has(p.part))
2023
+ ctx.addIssue({ code: "custom", path: ["parts", i, "part"], message: "is listed twice" });
2024
+ seen.add(p.part);
2025
+ });
2026
+ const counted2 = v.observed !== null && v.observed.counting.state === "counted";
2027
+ if (v.counted !== counted2) {
2028
+ ctx.addIssue({ code: "custom", path: ["counted"], message: "must be true exactly when the counts are counted" });
2029
+ }
2030
+ if (v.observed !== null && v.observed.phase !== v.phase) {
2031
+ ctx.addIssue({ code: "custom", path: ["observed", "phase"], message: "must be the phase under review" });
2032
+ }
2033
+ if (v.state === "closed") {
2034
+ v.parts.forEach((p, i) => {
2035
+ if (p.decision === void 0) {
2036
+ ctx.addIssue({ code: "custom", path: ["parts", i, "decision"], message: "every part of a closed review is decided" });
2037
+ }
2038
+ });
2039
+ }
2040
+ });
2041
+ }
2042
+ var phaseReviewOpenSchema = reviewedViewSchema("open");
2043
+ var phaseReviewClosedSchema = reviewedViewSchema("closed");
2044
+ var phaseReviewViewSchema = z13.discriminatedUnion("state", [
2045
+ phaseReviewNoneSchema,
2046
+ phaseReviewOpenSchema,
2047
+ phaseReviewClosedSchema
2048
+ ]);
2049
+
2050
+ // ../events/dist/build-record-v3.js
2051
+ var BUILD_RECORD_V3_FORMAT = "halfcycle-build-record/v3";
2052
+ var recordReviewRoundSchema = z14.object({
2053
+ round: z14.number().int().min(1),
2054
+ openedAt: utcIso8601,
2055
+ verdict: walkVerdictSchema,
2056
+ findings: z14.number().int().min(0)
2057
+ }).strict();
2058
+ var recordReviewApprovalSchema = z14.discriminatedUnion("kind", [
2059
+ z14.object({
2060
+ kind: z14.literal("approved"),
2061
+ planVersion: planVersionSchema,
2062
+ givenIn: scopeGivenInSchema,
2063
+ approvedAt: utcIso8601
2064
+ }).strict(),
2065
+ z14.object({ kind: z14.literal("none") }).strict()
2066
+ ]);
2067
+ var recordReviewGateSchema = z14.object({
2068
+ gate: boardGateNameSchema,
2069
+ state: boardStepStateSchema
2070
+ }).strict();
2071
+ var recordReviewQuestionSchema = z14.object({
2072
+ question: reviewNoteSchema,
2073
+ agentAnswer: reviewAgentAnswerSchema
2074
+ }).strict();
2075
+ var recordReviewPartSchema = z14.object({
2076
+ part: systemPartIdSchema,
2077
+ name: systemPartSchema.shape.name,
2078
+ change: reviewPartChangeSchema,
2079
+ built: z14.boolean(),
2080
+ builtWithoutApproval: z14.boolean(),
2081
+ decision: reviewDecisionSchema,
2082
+ reason: reviewNoteSchema.optional(),
2083
+ carriedTo: reviewCarriedToSchema.optional(),
2084
+ sentBack: z14.array(reviewSentBackSchema),
2085
+ questions: z14.array(recordReviewQuestionSchema),
2086
+ codeLink: partCodeLinkSchema
2087
+ }).strict().superRefine((v, ctx) => {
2088
+ const carriedOrDropped = v.decision === "carried" || v.decision === "dropped";
2089
+ if (v.reason !== void 0 !== carriedOrDropped) {
2090
+ ctx.addIssue({ code: "custom", path: ["reason"], message: "is present exactly on a carry or a drop" });
2091
+ }
2092
+ if (v.carriedTo !== void 0 !== (v.decision === "carried")) {
2093
+ ctx.addIssue({ code: "custom", path: ["carriedTo"], message: "is present exactly on a carry" });
2094
+ }
2095
+ });
2096
+ var SPINE_ORDER = boardGateNameSchema.options;
2097
+ var recordReviewGatesSchema = z14.array(recordReviewGateSchema).superRefine((gates, ctx) => {
2098
+ const inOrder = gates.length === SPINE_ORDER.length && gates.every((g, i) => g.gate === SPINE_ORDER[i]);
2099
+ if (!inOrder) {
2100
+ ctx.addIssue({ code: "custom", message: `must be exactly the five checkpoints in order: ${SPINE_ORDER.join(", ")}` });
2101
+ }
2102
+ });
2103
+ var recordReviewSchema = z14.object({
2104
+ sequence: z14.number().int().min(1),
2105
+ cycle: z14.number().int().min(0),
2106
+ rounds: z14.array(recordReviewRoundSchema).min(1),
2107
+ approval: recordReviewApprovalSchema,
2108
+ planVersion: planVersionSchema.nullable(),
2109
+ descriptionVersion: observedDescriptionVersionSchema,
2110
+ gates: recordReviewGatesSchema,
2111
+ observed: observedChangesSchema.nullable(),
2112
+ parts: z14.array(recordReviewPartSchema).max(REVIEW_PARTS_MAX)
2113
+ }).strict().superRefine((v, ctx) => {
2114
+ v.rounds.forEach((r, i) => {
2115
+ if (r.round !== i + 1) {
2116
+ ctx.addIssue({ code: "custom", path: ["rounds", i, "round"], message: "rounds must be numbered 1, 2, 3 \u2026 in order with no gap" });
2117
+ }
2118
+ });
2119
+ const seen = /* @__PURE__ */ new Set();
2120
+ v.parts.forEach((p, i) => {
2121
+ if (seen.has(p.part))
2122
+ ctx.addIssue({ code: "custom", path: ["parts", i, "part"], message: "is listed twice" });
2123
+ seen.add(p.part);
2124
+ if (v.observed === null && p.codeLink.kind !== "none") {
2125
+ ctx.addIssue({ code: "custom", path: ["parts", i, "codeLink"], message: "must be none when nothing was observed" });
2126
+ }
2127
+ });
2128
+ });
2129
+ var buildRecordV3Schema = z14.object({
2130
+ ...buildRecordV2Schema.shape,
2131
+ format: z14.literal(BUILD_RECORD_V3_FORMAT),
2132
+ review: recordReviewSchema
2133
+ }).strict().superRefine((record2, ctx) => {
2134
+ checkRecordHalves(record2, ctx);
2135
+ const closes = record2.observed.closes;
2136
+ const newest = closes[closes.length - 1].sequence;
2137
+ if (record2.review.sequence !== newest) {
2138
+ ctx.addIssue({ code: "custom", path: ["review", "sequence"], message: "must be the newest close" });
2139
+ }
2140
+ const at = record2.attested.closes.findIndex((c) => c.sequence === record2.review.sequence);
2141
+ const close = at === -1 ? void 0 : record2.attested.closes[at];
2142
+ if (close !== void 0 && close.decision !== "done-when-met") {
2143
+ ctx.addIssue({ code: "custom", path: ["attested", "closes", at, "decision"], message: "a close made by a review is done-when-met" });
2144
+ }
2145
+ const last = record2.review.rounds[record2.review.rounds.length - 1];
2146
+ if (close !== void 0 && last !== void 0) {
2147
+ const attested = close.walk === null ? null : close.walk.findings.length + close.walk.findingsNotShown;
2148
+ if (attested !== last.findings) {
2149
+ ctx.addIssue({
2150
+ code: "custom",
2151
+ path: ["review", "rounds", record2.review.rounds.length - 1, "findings"],
2152
+ message: "must equal the findings of the closing walk"
2153
+ });
2154
+ }
2155
+ }
2156
+ if (record2.review.observed !== null && record2.review.observed.phase !== record2.phase) {
2157
+ ctx.addIssue({ code: "custom", path: ["review", "observed", "phase"], message: "must be this record's phase" });
2158
+ }
2159
+ });
2160
+ var buildRecordSchema = z14.discriminatedUnion("format", [buildRecordV2Schema, buildRecordV3Schema]);
2161
+ function removalsOf(record2) {
2162
+ if (record2.format !== BUILD_RECORD_V3_FORMAT)
2163
+ return [];
2164
+ const out = [];
2165
+ for (const p of record2.review.parts) {
2166
+ if (p.change === "add" && p.decision === "dropped")
2167
+ out.push({ part: p.part, name: p.name, act: "dropped" });
2168
+ else if (p.change === "remove" && p.decision === "accepted")
2169
+ out.push({ part: p.part, name: p.name, act: "removed" });
2170
+ }
2171
+ return out;
2172
+ }
933
2173
 
934
2174
  // ../events/dist/device-auth.js
935
- import { z as z6 } from "zod";
936
- var utcIso86012 = z6.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
2175
+ import { z as z15 } from "zod";
2176
+ var utcIso86012 = z15.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp ending in Z");
937
2177
  var DEVICE_AUTH_STATUS = {
938
2178
  /** The poll: minted, nobody has acted yet. Keep polling. 200. */
939
2179
  PENDING: "pending",
@@ -974,25 +2214,25 @@ var DEVICE_POLL_REFUSALS = [
974
2214
  DEVICE_AUTH_STATUS.EXPIRED,
975
2215
  DEVICE_AUTH_STATUS.UNKNOWN
976
2216
  ];
977
- var deviceAuthStartResponseSchema = z6.object({
978
- deviceCode: z6.string().min(1),
979
- userCode: z6.string().min(1),
980
- verificationUrl: z6.string().min(1),
2217
+ var deviceAuthStartResponseSchema = z15.object({
2218
+ deviceCode: z15.string().min(1),
2219
+ userCode: z15.string().min(1),
2220
+ verificationUrl: z15.string().min(1),
981
2221
  expiresAt: utcIso86012,
982
- pollIntervalMs: z6.number().int().positive()
2222
+ pollIntervalMs: z15.number().int().positive()
983
2223
  }).strict();
984
- var devicePollPendingSchema = z6.object({
985
- status: z6.literal(DEVICE_AUTH_STATUS.PENDING),
986
- pollIntervalMs: z6.number().int().positive()
2224
+ var devicePollPendingSchema = z15.object({
2225
+ status: z15.literal(DEVICE_AUTH_STATUS.PENDING),
2226
+ pollIntervalMs: z15.number().int().positive()
987
2227
  }).strict();
988
- var devicePollApprovedSchema = z6.object({
989
- status: z6.literal(DEVICE_AUTH_STATUS.APPROVED),
990
- accountId: z6.string().min(1),
991
- credential: z6.string().min(1),
2228
+ var devicePollApprovedSchema = z15.object({
2229
+ status: z15.literal(DEVICE_AUTH_STATUS.APPROVED),
2230
+ accountId: z15.string().min(1),
2231
+ credential: z15.string().min(1),
992
2232
  expiresAt: utcIso86012
993
2233
  }).strict();
994
- var deviceAuthRefusalSchema = z6.object({
995
- status: z6.enum([
2234
+ var deviceAuthRefusalSchema = z15.object({
2235
+ status: z15.enum([
996
2236
  DEVICE_AUTH_STATUS.DECLINED,
997
2237
  DEVICE_AUTH_STATUS.EXPIRED,
998
2238
  DEVICE_AUTH_STATUS.UNKNOWN,
@@ -1003,25 +2243,25 @@ var deviceAuthRefusalSchema = z6.object({
1003
2243
  DEVICE_AUTH_STATUS.ALREADY_DECIDED,
1004
2244
  DEVICE_AUTH_STATUS.TERMS_REQUIRED
1005
2245
  ]),
1006
- message: z6.string().min(1)
2246
+ message: z15.string().min(1)
1007
2247
  }).strict();
1008
- var devicePollResponseSchema = z6.union([
2248
+ var devicePollResponseSchema = z15.union([
1009
2249
  devicePollPendingSchema,
1010
2250
  devicePollApprovedSchema,
1011
2251
  deviceAuthRefusalSchema
1012
2252
  ]);
1013
- var deviceConfirmRequestSchema = z6.object({
1014
- userCode: z6.string().min(1),
1015
- decision: z6.enum(["approve", "decline"]),
1016
- externalAuthId: z6.string().min(1).optional(),
1017
- email: z6.string().optional(),
1018
- acceptTerms: z6.boolean().optional()
2253
+ var deviceConfirmRequestSchema = z15.object({
2254
+ userCode: z15.string().min(1),
2255
+ decision: z15.enum(["approve", "decline"]),
2256
+ externalAuthId: z15.string().min(1).optional(),
2257
+ email: z15.string().optional(),
2258
+ acceptTerms: z15.boolean().optional()
1019
2259
  }).strict();
1020
- var deviceConfirmRecordedSchema = z6.object({
1021
- status: z6.literal(DEVICE_AUTH_STATUS.RECORDED),
1022
- accountId: z6.string().min(1).optional()
2260
+ var deviceConfirmRecordedSchema = z15.object({
2261
+ status: z15.literal(DEVICE_AUTH_STATUS.RECORDED),
2262
+ accountId: z15.string().min(1).optional()
1023
2263
  }).strict();
1024
- var deviceConfirmResponseSchema = z6.union([
2264
+ var deviceConfirmResponseSchema = z15.union([
1025
2265
  deviceConfirmRecordedSchema,
1026
2266
  deviceAuthRefusalSchema
1027
2267
  ]);
@@ -1033,20 +2273,20 @@ function projectPageUrl(engagementId) {
1033
2273
  }
1034
2274
 
1035
2275
  // ../events/dist/credential-wire.js
1036
- import { z as z7 } from "zod";
1037
- var engagementCredentialResponseSchema = z7.object({
1038
- engagementId: z7.string(),
1039
- sessionToken: z7.string(),
1040
- mcpUrl: z7.string(),
1041
- guardUrl: z7.string(),
1042
- controlTelemetryUrl: z7.string(),
1043
- methodVersion: z7.string().optional(),
1044
- consoleUrl: z7.string().optional()
2276
+ import { z as z16 } from "zod";
2277
+ var engagementCredentialResponseSchema = z16.object({
2278
+ engagementId: z16.string(),
2279
+ sessionToken: z16.string(),
2280
+ mcpUrl: z16.string(),
2281
+ guardUrl: z16.string(),
2282
+ controlTelemetryUrl: z16.string(),
2283
+ methodVersion: z16.string().optional(),
2284
+ consoleUrl: z16.string().optional()
1045
2285
  }).strict();
1046
- var ciTokenExchangeResponseSchema = z7.object({
1047
- token: z7.string(),
2286
+ var ciTokenExchangeResponseSchema = z16.object({
2287
+ token: z16.string(),
1048
2288
  expiresAt: utcIso8601,
1049
- engagementId: z7.string()
2289
+ engagementId: z16.string()
1050
2290
  }).strict();
1051
2291
  var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
1052
2292
  var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
@@ -1063,37 +2303,37 @@ var PLACEHOLDER_SENTENCES = Object.freeze({
1063
2303
  });
1064
2304
 
1065
2305
  // ../events/dist/phase-record.js
1066
- import { z as z8 } from "zod";
1067
- var phaseRecordRefusalReasonSchema = z8.enum([
2306
+ import { z as z17 } from "zod";
2307
+ var phaseRecordRefusalReasonSchema = z17.enum([
1068
2308
  "still-open",
1069
2309
  "reopened",
1070
2310
  "never-opened",
1071
2311
  "never-closed",
1072
2312
  "closed-before-records"
1073
2313
  ]);
1074
- var phaseRecordRecordSchema = z8.object({
1075
- outcome: z8.literal("record"),
2314
+ var phaseRecordRecordSchema = z17.object({
2315
+ outcome: z17.literal("record"),
1076
2316
  phase: phaseNameSchema,
1077
- record: buildRecordV2Schema
2317
+ record: buildRecordSchema
1078
2318
  }).strict().refine((body) => body.record.phase === body.phase, {
1079
2319
  message: "the record is for a different phase than the one asked for",
1080
2320
  path: ["record", "phase"]
1081
2321
  });
1082
- var phaseRecordWithheldSchema = z8.object({
1083
- outcome: z8.literal("withheld"),
2322
+ var phaseRecordWithheldSchema = z17.object({
2323
+ outcome: z17.literal("withheld"),
1084
2324
  phase: phaseNameSchema,
1085
- sequence: z8.number().int().min(1),
2325
+ sequence: z17.number().int().min(1),
1086
2326
  closedAt: utcIso8601,
1087
- message: z8.string()
2327
+ message: z17.string()
1088
2328
  }).strict();
1089
2329
  var phaseRecordRefusedSchema = wireErrorSchema.extend({
1090
- outcome: z8.literal("refused"),
2330
+ outcome: z17.literal("refused"),
1091
2331
  phase: phaseNameSchema,
1092
- statusCode: z8.literal(409),
1093
- error: z8.literal("PhaseRecordRefused"),
2332
+ statusCode: z17.literal(409),
2333
+ error: z17.literal("PhaseRecordRefused"),
1094
2334
  reason: phaseRecordRefusalReasonSchema
1095
2335
  }).strict();
1096
- var phaseRecordResponseSchema = z8.discriminatedUnion("outcome", [
2336
+ var phaseRecordResponseSchema = z17.discriminatedUnion("outcome", [
1097
2337
  phaseRecordRecordSchema,
1098
2338
  phaseRecordWithheldSchema,
1099
2339
  phaseRecordRefusedSchema
@@ -1147,6 +2387,7 @@ var ENGAGEMENT_ENV_KEYS = [
1147
2387
  ...GUARD_ENV_KEYS,
1148
2388
  "CONTROL_TELEMETRY_URL"
1149
2389
  ];
2390
+ var PHASE_STAMP_ENV_KEY = "HALFCYCLE_PHASE";
1150
2391
  function reconcileEnvText2(existing, values, keys = ENGAGEMENT_ENV_KEYS) {
1151
2392
  return reconcileEnvText(existing, values, keys);
1152
2393
  }
@@ -1247,7 +2488,7 @@ halfcycle_env_file() {
1247
2488
 
1248
2489
  // dist/identity.js
1249
2490
  import { execFileSync } from "node:child_process";
1250
- import { existsSync, linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2491
+ import { existsSync, linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
1251
2492
  import { dirname as dirname2, isAbsolute, join as join3 } from "node:path";
1252
2493
  import { randomUUID } from "node:crypto";
1253
2494
  var PROJECT_IDENTITY_NOTE = 'projectId is NOT the engagement id \u2014 it identifies this repository/worktree to Halfcycle, not an engagement. The engagement id lives in .halfcycle/bundle.json, in its "engagementId" field.';
@@ -1296,6 +2537,41 @@ function readRootCommit(targetRepoRoot) {
1296
2537
  function readRemote(targetRepoRoot) {
1297
2538
  return git(targetRepoRoot, ["remote", "get-url", "origin"]);
1298
2539
  }
2540
+ function stripUrlUserinfoTextually(trimmed) {
2541
+ const schemeEnd = trimmed.indexOf("://") + 3;
2542
+ const pathStart = (() => {
2543
+ const match = /[/?#]/.exec(trimmed.slice(schemeEnd));
2544
+ return match ? schemeEnd + match.index : trimmed.length;
2545
+ })();
2546
+ const authority = trimmed.slice(schemeEnd, pathStart);
2547
+ const at = authority.lastIndexOf("@");
2548
+ if (at === -1)
2549
+ return trimmed;
2550
+ return trimmed.slice(0, schemeEnd) + authority.slice(at + 1) + trimmed.slice(pathStart);
2551
+ }
2552
+ function redactRemoteCredential(remote) {
2553
+ if (remote === null)
2554
+ return null;
2555
+ const trimmed = remote.trim();
2556
+ if (trimmed.includes("://")) {
2557
+ try {
2558
+ const url = new URL(trimmed);
2559
+ if (url.username === "" && url.password === "")
2560
+ return trimmed;
2561
+ url.username = "";
2562
+ url.password = "";
2563
+ return url.toString();
2564
+ } catch {
2565
+ return stripUrlUserinfoTextually(trimmed);
2566
+ }
2567
+ }
2568
+ const at = trimmed.lastIndexOf("@");
2569
+ if (at === -1)
2570
+ return trimmed;
2571
+ const userinfo = trimmed.slice(0, at);
2572
+ const rest = trimmed.slice(at + 1);
2573
+ return userinfo.includes(":") && rest.includes(":") ? rest : trimmed;
2574
+ }
1299
2575
  function readIgnoredPaths(targetRepoRoot, paths) {
1300
2576
  if (paths.length === 0)
1301
2577
  return null;
@@ -1331,11 +2607,12 @@ function readIgnoredPaths(targetRepoRoot, paths) {
1331
2607
  function mintOrReadIdentity(targetRepoRoot) {
1332
2608
  const path = join3(targetRepoRoot, ".halfcycle", "project.json");
1333
2609
  if (existsSync(path)) {
2610
+ repairRemoteCredential(targetRepoRoot);
1334
2611
  const onDisk = JSON.parse(readFileSync2(path, "utf-8"));
1335
2612
  const identity = {
1336
2613
  projectId: onDisk.projectId ?? "",
1337
2614
  rootCommit: onDisk.rootCommit ?? null,
1338
- remote: onDisk.remote ?? null,
2615
+ remote: redactRemoteCredential(onDisk.remote ?? null),
1339
2616
  note: PROJECT_IDENTITY_NOTE
1340
2617
  };
1341
2618
  return { identity, minted: false };
@@ -1346,10 +2623,40 @@ function mintIdentity(targetRepoRoot) {
1346
2623
  return {
1347
2624
  projectId: randomUUID(),
1348
2625
  rootCommit: readRootCommit(targetRepoRoot),
1349
- remote: readRemote(targetRepoRoot),
2626
+ remote: redactRemoteCredential(readRemote(targetRepoRoot)),
1350
2627
  note: PROJECT_IDENTITY_NOTE
1351
2628
  };
1352
2629
  }
2630
+ function repairRemoteCredential(targetRepoRoot) {
2631
+ const path = join3(targetRepoRoot, ".halfcycle", "project.json");
2632
+ let onDisk;
2633
+ try {
2634
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
2635
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
2636
+ return;
2637
+ onDisk = parsed;
2638
+ } catch {
2639
+ return;
2640
+ }
2641
+ const storedRemote = onDisk.remote ?? null;
2642
+ const remote = redactRemoteCredential(storedRemote);
2643
+ if (remote === storedRemote)
2644
+ return;
2645
+ const identity = {
2646
+ projectId: onDisk.projectId ?? "",
2647
+ rootCommit: onDisk.rootCommit ?? null,
2648
+ remote,
2649
+ note: PROJECT_IDENTITY_NOTE
2650
+ };
2651
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
2652
+ try {
2653
+ writeFileSync2(temporary, serializeProjectIdentity(identity), { encoding: "utf-8", flag: "wx" });
2654
+ renameSync(temporary, path);
2655
+ } catch {
2656
+ } finally {
2657
+ rmSync(temporary, { force: true });
2658
+ }
2659
+ }
1353
2660
  function serializeProjectIdentity(identity) {
1354
2661
  return JSON.stringify(identity, null, 2) + "\n";
1355
2662
  }
@@ -2399,18 +3706,21 @@ exit 0
2399
3706
  var GENERATED_USER_PROMPT_SCOPE_HEADER = "# Typed scope approval \u2014 generated by";
2400
3707
  function generateUserPromptScopeHook() {
2401
3708
  return `#!/usr/bin/env bash
2402
- ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer.
3709
+ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the close review.
2403
3710
  #
2404
- # Lets the owner of this project approve a phase's scope, or send it back, by
2405
- # typing "approve <phase>" or "send back <phase>: <note>" as the whole prompt in
2406
- # their own Claude Code session.
3711
+ # Lets the owner of this project approve a phase's scope, or send it back, and
3712
+ # decide one part of a phase's close review, by typing one line as the whole
3713
+ # prompt in their own Claude Code session: "approve <phase>", "send back
3714
+ # <phase>: <note>", "accept <phase>/<part>", "question <phase>/<part>: <words>",
3715
+ # "carry <phase>/<part>: <words>" or "drop <phase>/<part>: <words>".
2407
3716
  #
2408
3717
  # AN ORDINARY PROMPT STARTS NOTHING. This script reads the hook input once and
2409
3718
  # looks only at the start of the prompt's value. Unless the prompt begins, after
2410
- # any whitespace (typed, or escaped in the input), with "approve " or
2411
- # "send back " in any case, it exits 0 with no output, and Node never starts.
2412
- # A quote inside any other field arrives escaped, so no other field (the folder
2413
- # the session is in, for one) can read as the start of the prompt.
3719
+ # any whitespace (typed, or escaped in the input), with "approve ", "send back ",
3720
+ # "accept ", "question ", "carry " or "drop ", in any case, it exits 0 with no
3721
+ # output, and Node never starts. A quote inside any other field arrives escaped,
3722
+ # so no other field (the folder the session is in, for one) can read as the
3723
+ # start of the prompt.
2414
3724
  #
2415
3725
  # Otherwise it finds the project root from its own location, never from the
2416
3726
  # folder the session has moved to, and hands the same input to the guard
@@ -2431,7 +3741,7 @@ IFS= read -r -d '' HC_INPUT || true
2431
3741
  # \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
2432
3742
  HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
2433
3743
  HC_LEAD="$HC_LEAD|"$'\\xc2\\xa0|\\xe1\\x9a\\x80|\\xe2\\x80[\\x80-\\x8a\\xa8\\xa9\\xaf]|\\xe2\\x81\\x9f|\\xe3\\x80\\x80|\\xef\\xbb\\xbf'
2434
- if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*(approve|send back) "; then
3744
+ if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*(approve|send back|accept|question|carry|drop) "; then
2435
3745
  exit 0
2436
3746
  fi
2437
3747
 
@@ -2711,8 +4021,10 @@ function checkDrift(targetRepoRoot) {
2711
4021
  return { drifted: installed !== current, installed, current, methodVersion: pin?.methodVersion ?? null };
2712
4022
  }
2713
4023
  function writeProjectIdentity(targetRepo) {
2714
- if (existsSync3(join5(targetRepo, PROJECT_IDENTITY_REL)))
4024
+ if (existsSync3(join5(targetRepo, PROJECT_IDENTITY_REL))) {
4025
+ repairRemoteCredential(targetRepo);
2715
4026
  return "skipped";
4027
+ }
2716
4028
  createProjectIdentityFile(targetRepo);
2717
4029
  return "written";
2718
4030
  }
@@ -3682,6 +4994,32 @@ function printRecordText(text) {
3682
4994
  });
3683
4995
  return out;
3684
4996
  }
4997
+ var MARKUP = /[\\`*_~$<>&[\]{}#|]/;
4998
+ function printStrictText(text) {
4999
+ if (text === "")
5000
+ return "(blank)";
5001
+ let out = "";
5002
+ for (const ch of text) {
5003
+ if (HIDDEN.test(ch))
5004
+ out += `U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
5005
+ else if (MARKUP.test(ch))
5006
+ out += `\\${ch}`;
5007
+ else
5008
+ out += ch;
5009
+ }
5010
+ return out;
5011
+ }
5012
+ function printItemStartText(text) {
5013
+ const out = printStrictText(text);
5014
+ if (out.startsWith(" "))
5015
+ return `&#32;${out.slice(1)}`;
5016
+ if (out.startsWith("-") || out.startsWith("+"))
5017
+ return `\\${out}`;
5018
+ const ordered = /^(\d{1,9})([.)])/.exec(out);
5019
+ if (ordered !== null)
5020
+ return `${ordered[1]}\\${out.slice(ordered[1].length)}`;
5021
+ return out;
5022
+ }
3685
5023
  function makePrinter() {
3686
5024
  let hidden = false;
3687
5025
  return {
@@ -3690,6 +5028,16 @@ function makePrinter() {
3690
5028
  hidden = true;
3691
5029
  return printRecordText(value);
3692
5030
  },
5031
+ strict(value) {
5032
+ if (!hidden && HIDDEN.test(value))
5033
+ hidden = true;
5034
+ return printStrictText(value);
5035
+ },
5036
+ itemStart(value) {
5037
+ if (!hidden && HIDDEN.test(value))
5038
+ hidden = true;
5039
+ return printItemStartText(value);
5040
+ },
3693
5041
  sawHidden() {
3694
5042
  return hidden;
3695
5043
  }
@@ -3905,6 +5253,52 @@ function renderClose(heading, observed, attested, checks, p) {
3905
5253
  renderStated(attested, p)
3906
5254
  ];
3907
5255
  }
5256
+ var REVIEW_LABEL = "**Recorded by Halfcycle from the owner's review.** Each part the review held, and how the project's owner decided it. A line marked as the agent's is the agent's own answer, not Halfcycle's.";
5257
+ var REVIEW_NO_PARTS = "The review had no parts, so it closed on one accept that names no part.";
5258
+ function approvalLine(approval) {
5259
+ if (approval.kind === "none")
5260
+ return "Built without an approved scope.";
5261
+ const where = approval.givenIn === "console" ? "in the console" : "in their own session";
5262
+ return `Scope approved by the project's owner, plan version ${String(approval.planVersion)}, ${where}.`;
5263
+ }
5264
+ function decisionText(part, p) {
5265
+ switch (part.decision) {
5266
+ case "accepted":
5267
+ return "Accepted";
5268
+ case "carried":
5269
+ return part.carriedTo === null || part.carriedTo === void 0 ? `Carried to the next phase planned: ${p.text(part.reason ?? "")}` : `Carried to phase ${String(part.carriedTo.number)}: ${p.text(part.reason ?? "")}`;
5270
+ case "dropped":
5271
+ return `Dropped: ${p.text(part.reason ?? "")}`;
5272
+ }
5273
+ }
5274
+ function reviewPartLines(part, p) {
5275
+ const lines = [`- ${p.itemStart(part.name)} \u2014 ${decisionText(part, p)}`];
5276
+ const code = codeLinkLine(part.codeLink);
5277
+ if (code !== void 0)
5278
+ lines.push(` - ${p.text(code)}`);
5279
+ for (const sent of part.sentBack)
5280
+ lines.push(` - Sent back in round ${String(sent.round)}: ${p.text(sent.note)}`);
5281
+ for (const q of part.questions) {
5282
+ lines.push(` - Question: ${p.text(q.question)}`);
5283
+ lines.push(` - The agent answered: ${p.text(q.agentAnswer)}`);
5284
+ }
5285
+ return lines;
5286
+ }
5287
+ function removalsLine(record2, p) {
5288
+ const removals = removalsOf(record2);
5289
+ if (removals.length === 0)
5290
+ return null;
5291
+ return `Take these parts out of docs/system.json: ${removals.map((r) => `"${p.strict(r.name)}" (${r.act})`).join(", ")}.`;
5292
+ }
5293
+ function renderReview(record2, p) {
5294
+ const review = record2.review;
5295
+ const blocks = ["## The owner's review", REVIEW_LABEL, approvalLine(review.approval)];
5296
+ blocks.push(review.parts.length === 0 ? REVIEW_NO_PARTS : review.parts.flatMap((part) => reviewPartLines(part, p)).join("\n"));
5297
+ const removals = removalsLine(record2, p);
5298
+ if (removals !== null)
5299
+ blocks.push(removals);
5300
+ return blocks;
5301
+ }
3908
5302
  function renderBuildRecordV2Markdown(record2) {
3909
5303
  const p = makePrinter();
3910
5304
  const attestedBySequence = /* @__PURE__ */ new Map();
@@ -3930,6 +5324,8 @@ function renderBuildRecordV2Markdown(record2) {
3930
5324
  ...renderChecks(record2.observed.checks, p),
3931
5325
  ...renderClose(total === 1 ? "## The close" : `## The latest close (close ${String(latest.sequence)} of ${String(total)})`, latest, attestedBySequence.get(latest.sequence), record2.observed.checks.state, p)
3932
5326
  ];
5327
+ if (record2.format === "halfcycle-build-record/v3")
5328
+ body.push(...renderReview(record2, p));
3933
5329
  if (total > 1) {
3934
5330
  body.push("## Earlier closes");
3935
5331
  for (const close of closes.slice(0, -1)) {
@@ -3944,7 +5340,7 @@ function renderBuildRecordV2Markdown(record2) {
3944
5340
  }
3945
5341
 
3946
5342
  // dist/build-record/write.js
3947
- import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, renameSync, rmSync as rmSync3, writeSync } from "node:fs";
5343
+ import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync3, writeSync } from "node:fs";
3948
5344
  import { basename, join as join8, resolve, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
3949
5345
  var BUILD_RECORD_DIR = join8("docs", "build-records");
3950
5346
  function assertInsideOutDir(outDir, candidate, identity) {
@@ -3970,7 +5366,7 @@ function createExclusiveOnDisk(path, data) {
3970
5366
  closeSync(fd);
3971
5367
  }
3972
5368
  function renameOnDisk(from, to) {
3973
- renameSync(from, to);
5369
+ renameSync2(from, to);
3974
5370
  }
3975
5371
  var NODE_RECORD_PAIR_IO = {
3976
5372
  createExclusive: createExclusiveOnDisk,
@@ -3996,7 +5392,7 @@ function isOwnEarlierRecord(existingText, served) {
3996
5392
  } catch {
3997
5393
  return false;
3998
5394
  }
3999
- const existing = buildRecordV2Schema.safeParse(parsedJson);
5395
+ const existing = buildRecordSchema.safeParse(parsedJson);
4000
5396
  if (!existing.success)
4001
5397
  return false;
4002
5398
  const old = existing.data;
@@ -4079,6 +5475,23 @@ function replaceOne(io, tmp, finalPath, text) {
4079
5475
  // dist/build-record/obtain.js
4080
5476
  import { relative as relative3 } from "node:path";
4081
5477
 
5478
+ // dist/open-phase.js
5479
+ function writePhaseStamp(engagementId, phase, home) {
5480
+ if (!isEngagementId(engagementId)) {
5481
+ return { path: "(none \u2014 no valid project id)", reason: new InvalidEngagementIdError(engagementId).message };
5482
+ }
5483
+ const path = engagementEnvPath(engagementId, home);
5484
+ if (phase !== null && !isValidPhaseIdentity(phase)) {
5485
+ return { path, reason: new InvalidPhaseIdentityError(phase).message };
5486
+ }
5487
+ try {
5488
+ writeEngagementEnv(engagementId, { [PHASE_STAMP_ENV_KEY]: phase }, home, [PHASE_STAMP_ENV_KEY]);
5489
+ return null;
5490
+ } catch (err) {
5491
+ return { path, reason: err instanceof Error ? err.message : String(err) };
5492
+ }
5493
+ }
5494
+
4082
5495
  // dist/missing-route.js
4083
5496
  var MESSAGE = {
4084
5497
  GET: /^Route GET .* not found\.$/s,
@@ -4105,7 +5518,13 @@ var REFUSAL_REASONS = phaseRecordRefusalReasonSchema.options;
4105
5518
  function buildRecordUrl(credential, phase) {
4106
5519
  return `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phases/${encodeURIComponent(phase)}/build-record`;
4107
5520
  }
4108
- async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_PAIR_IO) {
5521
+ function clearStampFor(engagementId, phase, home) {
5522
+ const stamped = readEngagementEnv(engagementId, home)?.[PHASE_STAMP_ENV_KEY];
5523
+ if (stamped !== phase)
5524
+ return null;
5525
+ return writePhaseStamp(engagementId, null, home);
5526
+ }
5527
+ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_PAIR_IO, home) {
4109
5528
  const url = buildRecordUrl(credential, phase);
4110
5529
  let res;
4111
5530
  let text;
@@ -4158,7 +5577,8 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
4158
5577
  kind: "written",
4159
5578
  jsonPath: relative3(repoRoot, result.jsonPath),
4160
5579
  mdPath: relative3(repoRoot, result.mdPath),
4161
- headline: latestCloseHeadline(answer.record)
5580
+ headline: latestCloseHeadline(answer.record),
5581
+ stampFailure: clearStampFor(credential.engagementId, phase, home)
4162
5582
  };
4163
5583
  }
4164
5584
  if (isMissingRoute("GET", res.status, raw)) {