halfcycle 0.4.8 → 0.4.10

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 (48) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/bin.bundle.mjs +2162 -335
  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/bin.js +0 -0
  15. package/dist/build-record/obtain.d.ts +14 -7
  16. package/dist/build-record/obtain.d.ts.map +1 -1
  17. package/dist/build-record/render-v2.d.ts +32 -6
  18. package/dist/build-record/render-v2.d.ts.map +1 -1
  19. package/dist/build-record/write.d.ts +2 -2
  20. package/dist/build-record/write.d.ts.map +1 -1
  21. package/dist/cli-contract.d.ts +2 -9
  22. package/dist/cli-contract.d.ts.map +1 -1
  23. package/dist/cli.js +3859 -1946
  24. package/dist/cli.js.map +3 -3
  25. package/dist/close-phase.d.ts +223 -134
  26. package/dist/close-phase.d.ts.map +1 -1
  27. package/dist/draw/index.d.ts +29 -6
  28. package/dist/draw/index.d.ts.map +1 -1
  29. package/dist/identity.d.ts +78 -9
  30. package/dist/identity.d.ts.map +1 -1
  31. package/dist/index.js +1685 -232
  32. package/dist/index.js.map +3 -3
  33. package/dist/install.d.ts +16 -0
  34. package/dist/install.d.ts.map +1 -1
  35. package/dist/open-phase.d.ts +25 -1
  36. package/dist/open-phase.d.ts.map +1 -1
  37. package/dist/plan.d.ts +75 -23
  38. package/dist/plan.d.ts.map +1 -1
  39. package/dist/pre-create-checks.d.ts.map +1 -1
  40. package/dist/review.d.ts +231 -0
  41. package/dist/review.d.ts.map +1 -0
  42. package/dist/shown-record.d.ts +46 -0
  43. package/dist/shown-record.d.ts.map +1 -0
  44. package/dist/system-report.d.ts +28 -4
  45. package/dist/system-report.d.ts.map +1 -1
  46. package/package.json +11 -11
  47. package/dist/project-page.d.ts +0 -24
  48. 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(["analyst-architect", "builder", "reviewer", "lead"]);
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", "gable"]);
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,30 @@ 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({
763
- // The agent's name. The same twenty-one on every project, so a name is a
813
+ var crewRoleSchema = z6.enum([
814
+ "business-analyst",
815
+ "architect",
816
+ "spec-writer",
817
+ "builder",
818
+ "context-docs",
819
+ "spec-reviewer",
820
+ "code-test-reviewer",
821
+ "delivery-lead",
822
+ "quality-lead"
823
+ ]);
824
+ var crewMemberSchema = z6.object({
825
+ // The agent's name. The same names on every project, so a name is a
764
826
  // vocabulary a returning client already knows.
765
- callsign: z5.string().min(1).max(32),
766
- // Which of the three headings this member reads under.
827
+ callsign: z6.string().min(1).max(32),
828
+ // Which of the four teams this member belongs to.
767
829
  group: crewGroupSchema,
830
+ // The part this member plays, which decides the stages it leads and helps with.
831
+ role: crewRoleSchema,
768
832
  // The kind of work this member takes. A task names a discipline, and only
769
833
  // agents holding it can be dispatched to it.
770
- discipline: z5.string().min(1).max(CREW_DISCIPLINE_MAX),
834
+ discipline: z6.string().min(1).max(CREW_DISCIPLINE_MAX),
771
835
  // How much thinking this member brings. There is no field naming the supplier
772
836
  // behind it, on this shape or on any other.
773
837
  tier: crewTierSchema,
@@ -776,10 +840,44 @@ var crewMemberSchema = z5.object({
776
840
  face: crewFaceSchema
777
841
  }).strict();
778
842
  var CREW_ROSTER = [
843
+ // ── analysts and architects ─────────────────────────────────────────────────
844
+ {
845
+ callsign: "SOFIA",
846
+ group: "analyst-architect",
847
+ role: "business-analyst",
848
+ discipline: "business analyst",
849
+ tier: "deep",
850
+ face: { head: "gable", antenna: "stalk", eyes: "dots", mouth: "wave", ears: false }
851
+ },
852
+ {
853
+ callsign: "BEA",
854
+ group: "analyst-architect",
855
+ role: "business-analyst",
856
+ discipline: "business analyst",
857
+ tier: "standard",
858
+ face: { head: "gable", antenna: "none", eyes: "visor", mouth: "line", ears: false }
859
+ },
860
+ {
861
+ callsign: "WREN",
862
+ group: "analyst-architect",
863
+ role: "architect",
864
+ discipline: "solution architect",
865
+ tier: "deep",
866
+ face: { head: "gable", antenna: "dish", eyes: "bars", mouth: "line", ears: true }
867
+ },
868
+ {
869
+ callsign: "PEI",
870
+ group: "analyst-architect",
871
+ role: "architect",
872
+ discipline: "technical architect",
873
+ tier: "standard",
874
+ face: { head: "gable", antenna: "twin", eyes: "cyclops", mouth: "dots", ears: false }
875
+ },
779
876
  // ── builders: code ──────────────────────────────────────────────────────────
780
877
  {
781
878
  callsign: "ADA",
782
879
  group: "builder",
880
+ role: "builder",
783
881
  discipline: "implementation",
784
882
  tier: "deep",
785
883
  face: { head: "sq", antenna: "stalk", eyes: "dots", mouth: "line", ears: false }
@@ -787,6 +885,7 @@ var CREW_ROSTER = [
787
885
  {
788
886
  callsign: "OTTO",
789
887
  group: "builder",
888
+ role: "builder",
790
889
  discipline: "implementation",
791
890
  tier: "deep",
792
891
  face: { head: "sq", antenna: "twin", eyes: "bars", mouth: "grid", ears: true }
@@ -794,6 +893,7 @@ var CREW_ROSTER = [
794
893
  {
795
894
  callsign: "MILO",
796
895
  group: "builder",
896
+ role: "builder",
797
897
  discipline: "implementation",
798
898
  tier: "standard",
799
899
  face: { head: "sqr", antenna: "stalk", eyes: "dots", mouth: "dots", ears: false }
@@ -801,42 +901,24 @@ var CREW_ROSTER = [
801
901
  {
802
902
  callsign: "NOVA",
803
903
  group: "builder",
904
+ role: "builder",
804
905
  discipline: "implementation",
805
906
  tier: "standard",
806
907
  face: { head: "sq", antenna: "dish", eyes: "cyclops", mouth: "line", ears: false }
807
908
  },
808
- {
809
- callsign: "KAI",
810
- group: "builder",
811
- discipline: "implementation",
812
- tier: "standard",
813
- face: { head: "sqr", antenna: "none", eyes: "bars", mouth: "line", ears: true }
814
- },
815
909
  {
816
910
  callsign: "HUGO",
817
911
  group: "builder",
912
+ role: "builder",
818
913
  discipline: "infrastructure",
819
914
  tier: "standard",
820
915
  face: { head: "sq", antenna: "twin", eyes: "dots", mouth: "grid", ears: true }
821
916
  },
822
- {
823
- callsign: "WREN",
824
- group: "builder",
825
- discipline: "refactor",
826
- tier: "fast",
827
- face: { head: "sqr", antenna: "stalk", eyes: "bars", mouth: "wave", ears: false }
828
- },
829
- {
830
- callsign: "PIP",
831
- group: "builder",
832
- discipline: "refactor",
833
- tier: "fast",
834
- face: { head: "sqr", antenna: "none", eyes: "dots", mouth: "dots", ears: false }
835
- },
836
917
  // ── builders: specs and docs ────────────────────────────────────────────────
837
918
  {
838
919
  callsign: "IRIS",
839
920
  group: "builder",
921
+ role: "spec-writer",
840
922
  discipline: "feature spec",
841
923
  tier: "deep",
842
924
  face: { head: "dome", antenna: "stalk", eyes: "visor", mouth: "line", ears: false }
@@ -844,6 +926,7 @@ var CREW_ROSTER = [
844
926
  {
845
927
  callsign: "JUNO",
846
928
  group: "builder",
929
+ role: "spec-writer",
847
930
  discipline: "feature spec",
848
931
  tier: "standard",
849
932
  face: { head: "dome", antenna: "none", eyes: "dots", mouth: "wave", ears: false }
@@ -851,6 +934,7 @@ var CREW_ROSTER = [
851
934
  {
852
935
  callsign: "LEX",
853
936
  group: "builder",
937
+ role: "context-docs",
854
938
  discipline: "context & docs",
855
939
  tier: "fast",
856
940
  face: { head: "dome", antenna: "twin", eyes: "bars", mouth: "dots", ears: false }
@@ -859,6 +943,7 @@ var CREW_ROSTER = [
859
943
  {
860
944
  callsign: "VERA",
861
945
  group: "reviewer",
946
+ role: "spec-reviewer",
862
947
  discipline: "spec consistency",
863
948
  tier: "deep",
864
949
  face: { head: "rnd", antenna: "stalk", eyes: "visor", mouth: "line", ears: false }
@@ -866,6 +951,7 @@ var CREW_ROSTER = [
866
951
  {
867
952
  callsign: "ODIN",
868
953
  group: "reviewer",
954
+ role: "spec-reviewer",
869
955
  discipline: "cross-spec audit",
870
956
  tier: "deep",
871
957
  face: { head: "rnd", antenna: "dish", eyes: "cyclops", mouth: "line", ears: false }
@@ -873,6 +959,7 @@ var CREW_ROSTER = [
873
959
  {
874
960
  callsign: "CASS",
875
961
  group: "reviewer",
962
+ role: "code-test-reviewer",
876
963
  discipline: "code review",
877
964
  tier: "deep",
878
965
  face: { head: "rnd", antenna: "none", eyes: "visor", mouth: "grid", ears: true }
@@ -880,13 +967,23 @@ var CREW_ROSTER = [
880
967
  {
881
968
  callsign: "THEO",
882
969
  group: "reviewer",
970
+ role: "code-test-reviewer",
883
971
  discipline: "code review",
884
972
  tier: "standard",
885
973
  face: { head: "rnd", antenna: "twin", eyes: "bars", mouth: "line", ears: false }
886
974
  },
975
+ {
976
+ callsign: "KAI",
977
+ group: "reviewer",
978
+ role: "code-test-reviewer",
979
+ discipline: "code review",
980
+ tier: "standard",
981
+ face: { head: "rnd", antenna: "none", eyes: "bars", mouth: "line", ears: true }
982
+ },
887
983
  {
888
984
  callsign: "ECHO",
889
985
  group: "reviewer",
986
+ role: "code-test-reviewer",
890
987
  discipline: "test quality",
891
988
  tier: "standard",
892
989
  face: { head: "rnd", antenna: "stalk", eyes: "dots", mouth: "wave", ears: false }
@@ -894,6 +991,7 @@ var CREW_ROSTER = [
894
991
  {
895
992
  callsign: "MAVIS",
896
993
  group: "reviewer",
994
+ role: "code-test-reviewer",
897
995
  discipline: "suites & coverage",
898
996
  tier: "standard",
899
997
  face: { head: "rnd", antenna: "none", eyes: "visor", mouth: "dots", ears: false }
@@ -901,6 +999,7 @@ var CREW_ROSTER = [
901
999
  {
902
1000
  callsign: "ZARA",
903
1001
  group: "reviewer",
1002
+ role: "code-test-reviewer",
904
1003
  discipline: "smoke & walk aid",
905
1004
  tier: "standard",
906
1005
  face: { head: "rnd", antenna: "dish", eyes: "bars", mouth: "wave", ears: false }
@@ -908,32 +1007,1206 @@ var CREW_ROSTER = [
908
1007
  {
909
1008
  callsign: "NORA",
910
1009
  group: "reviewer",
1010
+ role: "code-test-reviewer",
911
1011
  discipline: "evidence & provenance",
912
1012
  tier: "fast",
913
1013
  face: { head: "rnd", antenna: "twin", eyes: "dots", mouth: "line", ears: false }
914
1014
  },
915
- // ── always on: continuous, takes no task ────────────────────────────────────
1015
+ // ── leads ───────────────────────────────────────────────────────────────────
916
1016
  {
917
1017
  callsign: "ARGUS",
918
- group: "always-on",
919
- discipline: "guard \xB7 every diff",
1018
+ group: "lead",
1019
+ role: "quality-lead",
1020
+ discipline: "quality lead \xB7 every diff",
920
1021
  tier: "standard",
921
1022
  face: { head: "hex", antenna: "dish", eyes: "visor", mouth: "grid", ears: true }
922
1023
  },
923
1024
  {
924
1025
  callsign: "ATLAS",
925
- group: "always-on",
926
- discipline: "coordinator",
1026
+ group: "lead",
1027
+ role: "delivery-lead",
1028
+ discipline: "delivery lead",
927
1029
  tier: "deep",
928
1030
  face: { head: "hex", antenna: "twin", eyes: "cyclops", mouth: "line", ears: true }
929
1031
  }
930
1032
  ];
931
1033
  var CREW_ROSTER_SIZE = CREW_ROSTER.length;
932
1034
  var CREW_CALLSIGNS = CREW_ROSTER.map((member) => member.callsign);
1035
+ var crewLayerSchema = methodLayerSchema.extract(["l0", "l1", "l2", "l3", "l4", "l5"]);
1036
+ var crewRoleLayersSchema = z6.object({
1037
+ leads: z6.array(crewLayerSchema),
1038
+ helps: z6.array(crewLayerSchema)
1039
+ }).strict();
1040
+
1041
+ // ../events/dist/build-snapshot.js
1042
+ import { z as z8 } from "zod";
1043
+
1044
+ // ../events/dist/system-description.js
1045
+ import { z as z7 } from "zod";
1046
+ var SYSTEM_DESCRIPTION_FORMAT = 1;
1047
+ var SYSTEM_PARTS_MAX = 60;
1048
+ var SYSTEM_CONNECTIONS_MAX = 400;
1049
+ var SYSTEM_SLOT_MAX = 11;
1050
+ var SYSTEM_OWNS_MAX = 20;
1051
+ var SYSTEM_OWNED_PATTERN_MAX = 200;
1052
+ var SYSTEM_PART_NAME_MAX = 60;
1053
+ var SYSTEM_PART_DESCRIPTION_MAX = 160;
1054
+ var SYSTEM_CONNECTION_LABEL_MAX = 60;
1055
+ var SYSTEM_PART_ID_PATTERN = /^[a-z][a-z0-9-]{0,39}$/;
1056
+ var SYSTEM_LANES = ["clients", "edge", "services", "data", "external"];
1057
+ var systemLaneSchema = z7.enum(SYSTEM_LANES, {
1058
+ error: `must be one of ${SYSTEM_LANES.join(", ")}`
1059
+ });
1060
+ var SYSTEM_CONNECTION_KINDS = ["call", "event", "write", "external"];
1061
+ var systemConnectionKindSchema = z7.enum(SYSTEM_CONNECTION_KINDS, {
1062
+ error: `must be one of ${SYSTEM_CONNECTION_KINDS.join(", ")}`
1063
+ });
1064
+ var LINE_SEPARATOR = /[\u2028\u2029]/;
1065
+ function descriptionText(max) {
1066
+ return statementText(max).refine((v) => v.trim().length > 0, { message: "must not be empty or only spaces" }).refine((v) => !LINE_SEPARATOR.test(v), {
1067
+ message: "must be one line: it holds a line or paragraph separator"
1068
+ });
1069
+ }
1070
+ var systemPartIdSchema = z7.string().regex(SYSTEM_PART_ID_PATTERN, {
1071
+ message: "must be a short id: a lowercase letter, then lowercase letters, digits or hyphens, 40 characters at most"
1072
+ }).refine((v) => !looksLikeSecret(v), { message: "looks like a password or key; choose another id" });
1073
+ function hasEmptySegment(pattern) {
1074
+ return pattern.split("/").some((segment) => segment.length === 0);
1075
+ }
1076
+ function hasDotSegment(pattern) {
1077
+ return pattern.split("/").some((segment) => segment === "." || segment === "..");
1078
+ }
1079
+ function hasPartialDoubleStar(pattern) {
1080
+ return pattern.split("/").some((segment) => segment.includes("**") && segment !== "**");
1081
+ }
1082
+ var SEGMENT_CHARACTERS = /^[A-Za-z0-9._\-*]*$/;
1083
+ function hasForeignCharacter(pattern) {
1084
+ return pattern.split("/").some((segment) => !SEGMENT_CHARACTERS.test(segment));
1085
+ }
1086
+ var systemOwnedPatternSchema = z7.string().max(SYSTEM_OWNED_PATTERN_MAX, { message: `must be at most ${SYSTEM_OWNED_PATTERN_MAX} characters` }).refine((v) => !hasEmptySegment(v), {
1087
+ message: "must not start or end with a slash, or have two slashes together"
1088
+ }).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), {
1089
+ message: 'may use only letters, digits, ".", "_", "-" and "*" between slashes'
1090
+ }).refine((v) => !looksLikeSecret(v), {
1091
+ message: "this path looks like a credential; list a shorter folder above it"
1092
+ });
1093
+ var systemPartSchema = z7.object({
1094
+ id: systemPartIdSchema,
1095
+ name: descriptionText(SYSTEM_PART_NAME_MAX),
1096
+ description: descriptionText(SYSTEM_PART_DESCRIPTION_MAX),
1097
+ lane: systemLaneSchema,
1098
+ 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}` }),
1099
+ outside: z7.boolean(),
1100
+ owns: z7.array(systemOwnedPatternSchema).max(SYSTEM_OWNS_MAX, { message: `can list at most ${SYSTEM_OWNS_MAX} paths` })
1101
+ }).strict();
1102
+ var systemConnectionSchema = z7.object({
1103
+ from: systemPartIdSchema,
1104
+ to: systemPartIdSchema,
1105
+ kind: systemConnectionKindSchema,
1106
+ label: descriptionText(SYSTEM_CONNECTION_LABEL_MAX)
1107
+ }).strict();
1108
+ var systemDescriptionSchema = z7.object({
1109
+ format: z7.literal(SYSTEM_DESCRIPTION_FORMAT, { error: `must be ${SYSTEM_DESCRIPTION_FORMAT}` }),
1110
+ 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)` }),
1111
+ connections: z7.array(systemConnectionSchema).max(SYSTEM_CONNECTIONS_MAX, { message: `can hold at most ${SYSTEM_CONNECTIONS_MAX} connections` })
1112
+ }).strict();
1113
+ var systemDescriptionProblemSchema = z7.object({
1114
+ path: z7.string(),
1115
+ message: z7.string()
1116
+ }).strict();
1117
+ var systemReportRecordedSchema = z7.object({
1118
+ outcome: z7.literal("recorded"),
1119
+ version: z7.number().int().min(1),
1120
+ reportedAt: utcIso8601
1121
+ }).strict();
1122
+ var systemReportRefusedSchema = z7.object({
1123
+ outcome: z7.literal("refused"),
1124
+ problems: z7.array(systemDescriptionProblemSchema).min(1)
1125
+ }).strict();
1126
+ var systemReportUnavailableSchema = z7.object({
1127
+ outcome: z7.literal("unavailable")
1128
+ }).strict();
1129
+ var systemReportResultSchema = z7.discriminatedUnion("outcome", [
1130
+ systemReportRecordedSchema,
1131
+ systemReportRefusedSchema,
1132
+ systemReportUnavailableSchema
1133
+ ]);
1134
+ var SYSTEM_DRAW_SVG_MAX_BYTES = 2 * 1024 * 1024;
1135
+ var SYSTEM_DRAW_MERMAID_MAX_BYTES = 256 * 1024;
1136
+ var SYSTEM_DRAW_PNG_MAX_BYTES = 8 * 1024 * 1024;
1137
+ var systemDrawArchitectureRequestSchema = z7.object({
1138
+ picture: z7.literal("architecture"),
1139
+ version: z7.number().int().min(1)
1140
+ }).strict();
1141
+ var systemDrawPhaseRequestSchema = z7.object({
1142
+ picture: z7.literal("phase"),
1143
+ phase: phaseIdentitySchema,
1144
+ planVersion: z7.number().int().min(1)
1145
+ }).strict();
1146
+ var systemDrawClosedPhaseRequestSchema = z7.object({
1147
+ picture: z7.literal("closed-phase"),
1148
+ phase: phaseIdentitySchema
1149
+ }).strict();
1150
+ var systemDrawRequestSchema = z7.discriminatedUnion("picture", [
1151
+ systemDrawArchitectureRequestSchema,
1152
+ systemDrawPhaseRequestSchema,
1153
+ systemDrawClosedPhaseRequestSchema
1154
+ ]);
1155
+ function utf8ByteLength2(text) {
1156
+ let bytes = 0;
1157
+ for (let i = 0; i < text.length; i++) {
1158
+ const unit = text.charCodeAt(i);
1159
+ if (unit < 128)
1160
+ bytes += 1;
1161
+ else if (unit < 2048)
1162
+ bytes += 2;
1163
+ else if (unit >= 55296 && unit <= 56319 && i + 1 < text.length) {
1164
+ const next = text.charCodeAt(i + 1);
1165
+ if (next >= 56320 && next <= 57343) {
1166
+ bytes += 4;
1167
+ i++;
1168
+ } else {
1169
+ bytes += 3;
1170
+ }
1171
+ } else
1172
+ bytes += 3;
1173
+ }
1174
+ return bytes;
1175
+ }
1176
+ var BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
1177
+ function base64DecodedLength(text) {
1178
+ const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
1179
+ return text.length / 4 * 3 - padding;
1180
+ }
1181
+ var systemPictureSchema = z7.object({
1182
+ svg: z7.string().min(1).refine((v) => utf8ByteLength2(v) <= SYSTEM_DRAW_SVG_MAX_BYTES, { message: "the picture is larger than 2 MB" }),
1183
+ 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" })
1184
+ }).strict();
1185
+ var systemDrawRefusedSchema = z7.object({
1186
+ outcome: z7.literal("refused"),
1187
+ reason: z7.string().min(1)
1188
+ }).strict();
1189
+ var systemDrawUnavailableSchema = z7.object({
1190
+ outcome: z7.literal("unavailable")
1191
+ }).strict();
1192
+ var systemDrawNotClosedSchema = z7.object({
1193
+ outcome: z7.literal("not-closed")
1194
+ }).strict();
1195
+
1196
+ // ../events/dist/build-snapshot.js
1197
+ var buildTaskStateSchema = z8.enum([
1198
+ "pending",
1199
+ "in-flight",
1200
+ "completed",
1201
+ "failed",
1202
+ "blocked"
1203
+ ]);
1204
+ var BUILD_TASK_TITLE_MAX = 240;
1205
+ var BUILD_SNAPSHOT_TASK_LIMIT = 500;
1206
+ var BUILD_TASK_WHY_OUTSIDE_PLAN_MAX = 160;
1207
+ var BUILD_TASK_BUDGET_MAX_MINUTES = 10080;
1208
+ var BUILD_TASK_NOTE_MAX = 240;
1209
+ var BUILD_TASK_CLEAR_COMMAND_MAX = 240;
1210
+ var buildTaskKindSchema = z8.enum(["build", "review", "spec", "walk"]);
1211
+ var buildTaskIdSchema = z8.string().min(1).max(64);
1212
+ var buildTaskSchema = z8.object({
1213
+ // The task's own id, as the coordinator's graph names it.
1214
+ id: buildTaskIdSchema,
1215
+ // What the task is, in the coordinator's words. A description of the work,
1216
+ // bounded and scanned — never an excerpt of anybody's code.
1217
+ title: z8.string().min(1).max(BUILD_TASK_TITLE_MAX),
1218
+ // What the task is doing right now.
1219
+ state: buildTaskStateSchema,
1220
+ // Who holds it — a bare callsign, or the value naming a person when the work is
1221
+ // a person's. Absent when nothing has been dispatched to it yet.
1222
+ agent: agentNameSchema.optional(),
1223
+ // The kind of work the holder does. Present exactly when an agent holds it.
1224
+ discipline: z8.string().min(1).max(64).optional(),
1225
+ // How much thinking the holder brings. Present exactly when an agent holds it,
1226
+ // and there is no field naming the supplier behind it.
1227
+ tier: z8.string().min(1).max(32).optional(),
1228
+ // The tasks this one cannot start before. Always present; empty means it waits
1229
+ // on nothing, which is different from not having been computed.
1230
+ dependsOn: z8.array(buildTaskIdSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
1231
+ // The one task this task checks, when it is a check on other work. Absent on a
1232
+ // task that is doing the work rather than checking it.
1233
+ reviews: buildTaskIdSchema.optional(),
1234
+ // Which wave of the phase this task belongs to — the wave that writes the
1235
+ // specifications, or the wave that builds them. Absent when the graph does not
1236
+ // record it, which is different from the task belonging to neither.
1237
+ layer: methodLayerSchema.optional(),
1238
+ // When it was picked up, and when it settled. A reader computes how long it
1239
+ // took; neither this row nor this payload carries a duration.
1240
+ startedAt: utcIso8601.optional(),
1241
+ settledAt: utcIso8601.optional(),
1242
+ // The parts of this project's system the task builds, by their ids. Absent when
1243
+ // it builds none; never an empty list.
1244
+ builds: z8.array(systemPartIdSchema).min(1).max(SYSTEM_PARTS_MAX).optional(),
1245
+ // In one line, why the task built a part the phase's plan does not include, in
1246
+ // the words of whoever holds it. Absent when it gave no reason.
1247
+ whyOutsidePlan: descriptionText(BUILD_TASK_WHY_OUTSIDE_PLAN_MAX).optional(),
1248
+ // How long the task is expected to take, in whole minutes, from one minute to one
1249
+ // week. A plan, not a measurement; absent when no budget was set.
1250
+ budgetMinutes: z8.number().int().min(1).max(BUILD_TASK_BUDGET_MAX_MINUTES).optional(),
1251
+ // What kind of work the task is: building, checking other work, writing what to
1252
+ // build, or a person trying the product. Absent when the graph does not say.
1253
+ kind: buildTaskKindSchema.optional(),
1254
+ // One line from whoever holds the task about where it stands. Absent when there
1255
+ // is nothing to say.
1256
+ note: descriptionText(BUILD_TASK_NOTE_MAX).optional(),
1257
+ // On a task held for a person, the one thing they do or run to clear it, in one
1258
+ // line. Absent when the task names none.
1259
+ clearCommand: descriptionText(BUILD_TASK_CLEAR_COMMAND_MAX).optional(),
1260
+ // On a task held for a person, the crew member ready to take it on once it is
1261
+ // cleared, by callsign. Absent when nobody is standing by.
1262
+ standingBy: agentNameSchema.optional()
1263
+ }).strict().superRefine((task, ctx) => {
1264
+ if (task.reviews !== void 0 && task.reviews === task.id) {
1265
+ ctx.addIssue({
1266
+ code: z8.ZodIssueCode.custom,
1267
+ path: ["reviews"],
1268
+ message: `'${task.id}' reviews itself \u2014 a review names the OTHER task it checks.`
1269
+ });
1270
+ }
1271
+ if (task.layer !== void 0 && !isPhaseScopedLayer(task.layer)) {
1272
+ ctx.addIssue({
1273
+ code: z8.ZodIssueCode.custom,
1274
+ path: ["layer"],
1275
+ message: `'${task.layer}' is not a wave a phase runs \u2014 expected one of ${PHASE_SCOPED_LAYERS.join(", ")}.`
1276
+ });
1277
+ }
1278
+ const profiled = task.agent !== void 0 && task.agent !== HUMAN_AGENT;
1279
+ for (const field of ["discipline", "tier"]) {
1280
+ if (profiled && task[field] === void 0) {
1281
+ ctx.addIssue({
1282
+ code: z8.ZodIssueCode.custom,
1283
+ path: [field],
1284
+ message: `'${field}' is required on a task an agent holds.`
1285
+ });
1286
+ }
1287
+ if (!profiled && task[field] !== void 0) {
1288
+ ctx.addIssue({
1289
+ code: z8.ZodIssueCode.custom,
1290
+ path: [field],
1291
+ message: task.agent === void 0 ? `'${field}' must be absent on a task nobody holds.` : `'${field}' must be absent on work a person holds.`
1292
+ });
1293
+ }
1294
+ }
1295
+ });
1296
+ var buildPhaseSchema = z8.string().min(1).max(128);
1297
+ var BUILD_PHASE_TITLE_MAX = 160;
1298
+ var buildPhaseTitleSchema = z8.string().min(1).max(BUILD_PHASE_TITLE_MAX);
1299
+ var buildTaskListSchema = z8.array(buildTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT).superRefine((tasks, ctx) => {
1300
+ const seen = /* @__PURE__ */ new Set();
1301
+ for (const [index, task] of tasks.entries()) {
1302
+ if (seen.has(task.id)) {
1303
+ ctx.addIssue({
1304
+ code: z8.ZodIssueCode.custom,
1305
+ path: [index, "id"],
1306
+ message: `duplicate task id '${task.id}' \u2014 one graph names each task once.`
1307
+ });
1308
+ }
1309
+ seen.add(task.id);
1310
+ }
1311
+ });
1312
+ var buildTaskMarkSchema = z8.object({
1313
+ // The task that was marked.
1314
+ task: buildTaskIdSchema,
1315
+ // When it was first marked.
1316
+ at: utcIso8601
1317
+ }).strict();
1318
+ var buildSnapshotSchema = z8.object({
1319
+ // Which phase this graph belongs to.
1320
+ phase: buildPhaseSchema,
1321
+ // That phase's name in words, when the project gives it one.
1322
+ phaseTitle: buildPhaseTitleSchema.optional(),
1323
+ // Every task on it. Never a partial graph and never a change since last time.
1324
+ tasks: buildTaskListSchema,
1325
+ // When this graph was last reported.
1326
+ reportedAt: utcIso8601,
1327
+ // When work on this phase was first reported with no approval of its scope in force.
1328
+ builtWithoutApproval: utcIso8601.optional(),
1329
+ // Each task that started on a part still waiting for approval, and when it was
1330
+ // first seen doing so. Absent when there is none.
1331
+ tasksBuiltWithoutApproval: z8.array(buildTaskMarkSchema).min(1).max(BUILD_SNAPSHOT_TASK_LIMIT).optional()
1332
+ }).strict();
1333
+ var buildPhaseEntrySchema = z8.object({
1334
+ // Which phase has a stored graph.
1335
+ phase: buildPhaseSchema,
1336
+ // That phase's name in words, when the project gave it one.
1337
+ phaseTitle: buildPhaseTitleSchema.optional(),
1338
+ // When that phase's graph was last reported.
1339
+ reportedAt: utcIso8601,
1340
+ // Present when work on this phase was reported with no approval of its scope in force.
1341
+ builtWithoutApproval: z8.literal(true).optional()
1342
+ }).strict();
1343
+ var buildSnapshotReportSchema = z8.object({
1344
+ // Which phase this graph belongs to.
1345
+ phase: buildPhaseSchema,
1346
+ // That phase's name in words, when the project gives it one.
1347
+ phaseTitle: buildPhaseTitleSchema.optional(),
1348
+ // The whole graph, every time. Reporting a change instead of the graph is the
1349
+ // one thing this route does not accept.
1350
+ tasks: buildTaskListSchema
1351
+ }).strict();
1352
+ var buildSnapshotReportResultSchema = z8.object({
1353
+ // What happened. One outcome today; a refusal travels as the shared wire error.
1354
+ outcome: z8.literal("recorded"),
1355
+ // Which phase the graph was recorded against.
1356
+ phase: buildPhaseSchema,
1357
+ // When it was recorded.
1358
+ reportedAt: utcIso8601
1359
+ }).strict();
1360
+
1361
+ // ../events/dist/phase-plan.js
1362
+ import { z as z9 } from "zod";
1363
+ var PHASE_PLAN_FORMAT = 1;
1364
+ var PHASE_PLAN_CHANGES_MAX = 60;
1365
+ var PHASE_PLAN_SUMMARY_MAX = 160;
1366
+ var SCOPE_NOTE_MAX = 500;
1367
+ var PHASE_PLAN_PHASE_MAX = buildPhaseSchema.maxLength ?? 0;
1368
+ var PHASE_PLAN_CHANGE_KINDS = ["add", "change", "remove"];
1369
+ var phasePlanChangeSchema = z9.enum(PHASE_PLAN_CHANGE_KINDS, {
1370
+ error: `must be one of ${PHASE_PLAN_CHANGE_KINDS.join(", ")}`
1371
+ });
1372
+ var phasePlanPhaseSchema = phaseIdentitySchema.refine((v) => !isValidPhaseIdentity(v) || buildPhaseSchema.safeParse(String(v)).success, { message: `must be at most ${PHASE_PLAN_PHASE_MAX} characters` });
1373
+ var phasePlanSummarySchema = descriptionText(PHASE_PLAN_SUMMARY_MAX);
1374
+ var phasePlanEntrySchema = z9.object({
1375
+ part: systemPartIdSchema,
1376
+ change: phasePlanChangeSchema,
1377
+ summary: phasePlanSummarySchema
1378
+ }).strict();
1379
+ var phasePlanSchema = z9.object({
1380
+ format: z9.literal(PHASE_PLAN_FORMAT, { error: `must be ${PHASE_PLAN_FORMAT}` }),
1381
+ phase: phasePlanPhaseSchema,
1382
+ 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` })
1383
+ }).strict();
1384
+ var scopePhaseSchema = buildPhaseSchema.refine((v) => isValidPhaseIdentity(v), {
1385
+ message: "must be a phase name"
1386
+ });
1387
+ var SCOPE_STATE_NAMES = [
1388
+ "not-planned",
1389
+ "waiting",
1390
+ "sent-back",
1391
+ "approved",
1392
+ "partly-stale"
1393
+ ];
1394
+ var scopeStateNameSchema = z9.enum(SCOPE_STATE_NAMES);
1395
+ var plannedScopeStateSchema = scopeStateNameSchema.exclude(["not-planned"]);
1396
+ var SCOPE_PART_STATUSES = ["approved", "waiting"];
1397
+ var scopePartStatusSchema = z9.enum(SCOPE_PART_STATUSES);
1398
+ var SCOPE_ACTS = ["approve", "send-back"];
1399
+ var scopeActSchema = z9.enum(SCOPE_ACTS);
1400
+ var SCOPE_GIVEN_IN = ["console", "chat"];
1401
+ var scopeGivenInSchema = z9.enum(SCOPE_GIVEN_IN);
1402
+ var scopeNoteSchema = descriptionText(SCOPE_NOTE_MAX);
1403
+ var scopeNoteWireSchema = z9.string().max(SCOPE_NOTE_MAX);
1404
+ var SCOPE_PART_VERDICTS = ["ok", "change"];
1405
+ var scopePartVerdictSchema = z9.enum(SCOPE_PART_VERDICTS);
1406
+ var scopeDecisionPartSchema = z9.object({
1407
+ part: systemPartIdSchema,
1408
+ verdict: scopePartVerdictSchema,
1409
+ note: scopeNoteWireSchema.optional()
1410
+ }).strict();
1411
+ var planVersionSchema = z9.number().int().min(1);
1412
+ var carriedPhaseSchema = z9.object({
1413
+ phase: scopePhaseSchema,
1414
+ number: z9.number().int().min(1)
1415
+ }).strict();
1416
+ var carriedFromEntrySchema = carriedPhaseSchema.extend({
1417
+ part: systemPartIdSchema
1418
+ }).strict();
1419
+ var phasePlanRequestSchema = z9.object({
1420
+ descriptionVersion: z9.number().int().min(1),
1421
+ plan: z9.unknown()
1422
+ }).strict();
1423
+ var phasePlanRecordedSchema = z9.object({
1424
+ outcome: z9.literal("recorded"),
1425
+ version: planVersionSchema,
1426
+ phaseNumber: z9.number().int().min(1),
1427
+ state: plannedScopeStateSchema,
1428
+ changedParts: z9.array(systemPartIdSchema).max(PHASE_PLAN_CHANGES_MAX),
1429
+ typedApprovalOffered: z9.boolean(),
1430
+ reportedAt: utcIso8601,
1431
+ carriedFrom: z9.array(carriedFromEntrySchema).min(1).max(PHASE_PLAN_CHANGES_MAX).optional()
1432
+ }).strict();
1433
+ var phasePlanRefusedSchema = z9.object({
1434
+ outcome: z9.literal("refused"),
1435
+ problems: z9.array(systemDescriptionProblemSchema).min(1)
1436
+ }).strict();
1437
+ var phasePlanUnavailableSchema = z9.object({
1438
+ outcome: z9.literal("unavailable")
1439
+ }).strict();
1440
+ var REMOVAL_OWED_ACTS = ["dropped", "removed"];
1441
+ var removalOwedPartSchema = z9.object({
1442
+ part: systemPartIdSchema,
1443
+ name: systemPartSchema.shape.name,
1444
+ phase: scopePhaseSchema,
1445
+ number: z9.number().int().min(1),
1446
+ act: z9.enum(REMOVAL_OWED_ACTS)
1447
+ }).strict();
1448
+ var phasePlanRemovalOwedSchema = z9.object({
1449
+ outcome: z9.literal("removal-owed"),
1450
+ parts: z9.array(removalOwedPartSchema).min(1).max(SYSTEM_PARTS_MAX)
1451
+ }).strict();
1452
+ var phasePlanResultSchema = z9.discriminatedUnion("outcome", [
1453
+ phasePlanRecordedSchema,
1454
+ phasePlanRefusedSchema,
1455
+ phasePlanUnavailableSchema,
1456
+ phasePlanRemovalOwedSchema
1457
+ ]);
1458
+ var scopeApproveRequestSchema = z9.object({
1459
+ act: z9.literal("approve"),
1460
+ planVersion: planVersionSchema,
1461
+ parts: z9.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX)
1462
+ }).strict();
1463
+ var scopeSendBackRequestSchema = z9.object({
1464
+ act: z9.literal("send-back"),
1465
+ planVersion: planVersionSchema,
1466
+ parts: z9.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
1467
+ note: scopeNoteWireSchema.optional()
1468
+ }).strict();
1469
+ var scopeDecisionRequestSchema = z9.discriminatedUnion("act", [
1470
+ scopeApproveRequestSchema,
1471
+ scopeSendBackRequestSchema
1472
+ ]);
1473
+ var scopeDecisionRecordedSchema = z9.object({
1474
+ outcome: z9.literal("recorded"),
1475
+ act: scopeActSchema,
1476
+ phase: scopePhaseSchema,
1477
+ planVersion: planVersionSchema,
1478
+ state: plannedScopeStateSchema,
1479
+ decidedAt: utcIso8601
1480
+ }).strict();
1481
+ var scopeDecisionAlreadyApprovedSchema = z9.object({
1482
+ outcome: z9.literal("already-approved"),
1483
+ replanNeeded: z9.boolean(),
1484
+ reason: z9.string().min(1)
1485
+ }).strict();
1486
+ var scopeDecisionRefusedSchema = z9.object({
1487
+ outcome: z9.literal("refused"),
1488
+ problems: z9.array(systemDescriptionProblemSchema).min(1)
1489
+ }).strict();
1490
+ var scopeDecisionUnavailableSchema = z9.object({
1491
+ outcome: z9.literal("unavailable")
1492
+ }).strict();
1493
+ function reasonOutcome(outcome) {
1494
+ return z9.object({ outcome: z9.literal(outcome), reason: z9.string().min(1) }).strict();
1495
+ }
1496
+ var scopeDecisionResultSchema = z9.discriminatedUnion("outcome", [
1497
+ scopeDecisionRecordedSchema,
1498
+ scopeDecisionAlreadyApprovedSchema,
1499
+ scopeDecisionRefusedSchema,
1500
+ scopeDecisionUnavailableSchema,
1501
+ reasonOutcome("not-owner"),
1502
+ reasonOutcome("no-owner"),
1503
+ reasonOutcome("plan-changed"),
1504
+ reasonOutcome("build-started")
1505
+ ]);
1506
+ var scopeReadPartSchema = z9.object({
1507
+ part: systemPartIdSchema,
1508
+ name: systemPartSchema.shape.name,
1509
+ change: phasePlanChangeSchema,
1510
+ summary: phasePlanSummarySchema,
1511
+ status: scopePartStatusSchema,
1512
+ changed: z9.boolean(),
1513
+ carriedFrom: carriedPhaseSchema.optional()
1514
+ }).strict();
1515
+ var scopeReadDecisionSchema = z9.object({
1516
+ act: scopeActSchema,
1517
+ planVersion: planVersionSchema,
1518
+ givenIn: scopeGivenInSchema,
1519
+ at: utcIso8601,
1520
+ parts: z9.array(scopeDecisionPartSchema).max(PHASE_PLAN_CHANGES_MAX),
1521
+ note: scopeNoteWireSchema.optional()
1522
+ }).strict();
1523
+ var scopeReadNotPlannedSchema = z9.object({
1524
+ phase: scopePhaseSchema,
1525
+ state: z9.literal("not-planned"),
1526
+ canDecide: z9.boolean()
1527
+ }).strict();
1528
+ var scopeReadPlannedSchema = z9.object({
1529
+ phase: scopePhaseSchema,
1530
+ state: plannedScopeStateSchema,
1531
+ phaseNumber: z9.number().int().min(1),
1532
+ planVersion: planVersionSchema,
1533
+ parts: z9.array(scopeReadPartSchema).min(1).max(PHASE_PLAN_CHANGES_MAX),
1534
+ decisions: z9.array(scopeReadDecisionSchema),
1535
+ canDecide: z9.boolean(),
1536
+ replanLine: z9.string().min(1).optional()
1537
+ }).strict();
1538
+ var scopeReadSchema = z9.union([scopeReadNotPlannedSchema, scopeReadPlannedSchema]);
1539
+
1540
+ // ../events/dist/build-record-v3.js
1541
+ import { z as z14 } from "zod";
1542
+
1543
+ // ../events/dist/board-vocabulary.js
1544
+ import { z as z10 } from "zod";
1545
+ var boardStepStateSchema = z10.enum([
1546
+ "not-instrumented",
1547
+ "done",
1548
+ "did-not-pass",
1549
+ "could-not-complete",
1550
+ "not-yet-available",
1551
+ "qualified"
1552
+ ]);
1553
+ var boardGateNameSchema = z10.enum([
1554
+ "scope",
1555
+ "architecture",
1556
+ "specs",
1557
+ "taskgraph",
1558
+ "phase-close"
1559
+ ]);
1560
+
1561
+ // ../events/dist/code-link.js
1562
+ import { z as z11 } from "zod";
1563
+ 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" });
1564
+ var codeLinkFactsSchema = z11.object({
1565
+ repositoryAddress: codeLinkAddressSchema.nullable(),
1566
+ headOnGitHub: z11.boolean()
1567
+ }).strict().refine((v) => !(v.headOnGitHub && v.repositoryAddress === null), {
1568
+ message: "cannot be true when the code is not on GitHub",
1569
+ path: ["headOnGitHub"]
1570
+ });
1571
+ var PART_CODE_LINK_SCOPES = ["part", "phase"];
1572
+ var PART_CODE_LINK_RANGE_REASONS = ["not-github", "not-pushed"];
1573
+ var PART_CODE_LINK_URL_PREFIX = "https://github.com/";
1574
+ var partCodeLinkGithubSchema = z11.object({
1575
+ kind: z11.literal("github"),
1576
+ scope: z11.enum(PART_CODE_LINK_SCOPES),
1577
+ url: z11.string().startsWith(PART_CODE_LINK_URL_PREFIX, { message: "must start with https://github.com/" })
1578
+ }).strict();
1579
+ var partCodeLinkRangeSchema = z11.object({
1580
+ kind: z11.literal("range"),
1581
+ why: z11.enum(PART_CODE_LINK_RANGE_REASONS),
1582
+ from: closeCommitSchema,
1583
+ to: closeCommitSchema
1584
+ }).strict();
1585
+ var partCodeLinkNoneSchema = z11.object({
1586
+ kind: z11.literal("none")
1587
+ }).strict();
1588
+ var partCodeLinkSchema = z11.discriminatedUnion("kind", [
1589
+ partCodeLinkGithubSchema,
1590
+ partCodeLinkRangeSchema,
1591
+ partCodeLinkNoneSchema
1592
+ ]);
1593
+
1594
+ // ../events/dist/observed-changes.js
1595
+ import { z as z12 } from "zod";
1596
+ var OBSERVED_PATHS_LISTED_MAX = 50;
1597
+ var OBSERVED_PATH_MAX = 300;
1598
+ var OBSERVED_COUNT_MAX = 1e6;
1599
+ 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}` });
1600
+ var observedDescriptionVersionSchema = z12.number().int().min(1);
1601
+ var observedPathSchema = z12.string().min(1, { message: "must not be empty" }).max(OBSERVED_PATH_MAX, { message: `must be at most ${OBSERVED_PATH_MAX} characters` });
1602
+ 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" });
1603
+ var observedMethodFilesSchema = z12.object({
1604
+ added: observedCountSchema,
1605
+ changed: observedCountSchema,
1606
+ removed: observedCountSchema
1607
+ }).strict();
1608
+ var observedFilesSchema = z12.object({
1609
+ added: observedCountSchema,
1610
+ changed: observedCountSchema,
1611
+ removed: observedCountSchema,
1612
+ testFilesAdded: observedCountSchema
1613
+ }).strict().refine((v) => v.testFilesAdded <= v.added, {
1614
+ message: "cannot be more than the files added",
1615
+ path: ["testFilesAdded"]
1616
+ });
1617
+ function filesChanged(files) {
1618
+ return files.added + files.changed + files.removed;
1619
+ }
1620
+ function pathsFitTheCount(files, paths) {
1621
+ return files === null ? paths.length === 0 : paths.length <= filesChanged(files);
1622
+ }
1623
+ function checkPartsUnique(parts, ctx, at) {
1624
+ const seen = /* @__PURE__ */ new Set();
1625
+ parts.forEach((entry, i) => {
1626
+ if (seen.has(entry.part)) {
1627
+ ctx.addIssue({ code: "custom", path: [...at, i, "part"], message: `names the part "${entry.part}" twice` });
1628
+ }
1629
+ seen.add(entry.part);
1630
+ });
1631
+ }
1632
+ var observedBasisCountSchema = z12.object({
1633
+ outcome: z12.literal("count"),
1634
+ phase: phaseNameSchema,
1635
+ startCommit: closeCommitSchema,
1636
+ descriptionVersion: observedDescriptionVersionSchema,
1637
+ description: systemDescriptionSchema
1638
+ }).strict();
1639
+ var observedBasisCannotCountSchema = z12.object({
1640
+ outcome: z12.literal("cannot-count"),
1641
+ phase: phaseNameSchema,
1642
+ reason: z12.literal("no-start-commit")
1643
+ }).strict();
1644
+ var observedBasisNothingToCountSchema = z12.object({
1645
+ outcome: z12.literal("nothing-to-count"),
1646
+ reason: z12.enum(["no-open-phase", "no-description"])
1647
+ }).strict();
1648
+ var observedBasisSchema = z12.discriminatedUnion("outcome", [
1649
+ observedBasisCountSchema,
1650
+ observedBasisCannotCountSchema,
1651
+ observedBasisNothingToCountSchema
1652
+ ]);
1653
+ var observedReportPartSchema = z12.object({
1654
+ part: systemPartIdSchema,
1655
+ added: observedCountSchema,
1656
+ changed: observedCountSchema,
1657
+ removed: observedCountSchema,
1658
+ testFilesAdded: observedCountSchema
1659
+ }).strict().refine((v) => v.testFilesAdded <= v.added, {
1660
+ message: "cannot be more than the files added",
1661
+ path: ["testFilesAdded"]
1662
+ }).refine((v) => filesChanged(v) >= 1, {
1663
+ message: "a part is listed only when at least one file changed under it",
1664
+ path: ["added"]
1665
+ });
1666
+ var observedReportOutsideSchema = z12.object({
1667
+ added: observedCountSchema,
1668
+ changed: observedCountSchema,
1669
+ removed: observedCountSchema,
1670
+ testFilesAdded: observedCountSchema,
1671
+ paths: observedPathListSchema
1672
+ }).strict().refine((v) => v.testFilesAdded <= v.added, {
1673
+ message: "cannot be more than the files added",
1674
+ path: ["testFilesAdded"]
1675
+ }).refine((v) => pathsFitTheCount(v, v.paths), {
1676
+ message: "lists more paths than files changed",
1677
+ path: ["paths"]
1678
+ });
1679
+ var observedChangesReportSchema = z12.object({
1680
+ phase: phaseNameSchema,
1681
+ startCommit: closeCommitSchema,
1682
+ headCommit: closeCommitSchema,
1683
+ headCommittedAt: utcIso8601,
1684
+ commits: observedCountSchema,
1685
+ descriptionVersion: observedDescriptionVersionSchema,
1686
+ parts: z12.array(observedReportPartSchema).max(SYSTEM_PARTS_MAX, { message: `can list at most ${SYSTEM_PARTS_MAX} parts` }),
1687
+ methodFiles: observedMethodFilesSchema,
1688
+ outsideEveryPart: observedReportOutsideSchema,
1689
+ codeLink: codeLinkFactsSchema
1690
+ }).strict().superRefine((v, ctx) => checkPartsUnique(v.parts, ctx, ["parts"]));
1691
+ var observedSaidSchema = z12.object({
1692
+ title: buildTaskSchema.shape.title,
1693
+ agent: buildTaskSchema.shape.agent,
1694
+ whyOutsidePlan: buildTaskSchema.shape.whyOutsidePlan
1695
+ }).strict();
1696
+ var observedNotInPlanSchema = z12.object({
1697
+ part: systemPartIdSchema,
1698
+ name: systemPartSchema.shape.name,
1699
+ said: z12.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT)
1700
+ }).strict();
1701
+ var observedChangesRecordedSchema = z12.object({
1702
+ outcome: z12.literal("recorded"),
1703
+ notInPlan: z12.array(observedNotInPlanSchema).max(SYSTEM_PARTS_MAX).superRefine((parts, ctx) => checkPartsUnique(parts, ctx, [])),
1704
+ outside: z12.object({
1705
+ files: observedCountSchema,
1706
+ example: observedPathSchema.nullable()
1707
+ }).strict().refine((v) => v.example === null || v.files >= 1, {
1708
+ message: "names an example when no file changed",
1709
+ path: ["example"]
1710
+ })
1711
+ }).strict();
1712
+ var observedChangesKeptSchema = z12.object({
1713
+ outcome: z12.literal("kept")
1714
+ }).strict();
1715
+ var observedChangesReportResultSchema = z12.discriminatedUnion("outcome", [
1716
+ observedChangesRecordedSchema,
1717
+ observedChangesKeptSchema
1718
+ ]);
1719
+ var OBSERVE_NOT_SENT_REASONS = [
1720
+ "not-configured",
1721
+ "nothing-to-count",
1722
+ "cannot-count",
1723
+ "start-not-here",
1724
+ "start-not-on-branch",
1725
+ "basis-moved",
1726
+ "refused",
1727
+ "unreachable"
1728
+ ];
1729
+ var observeRunRecordedSchema = z12.object({ outcome: z12.literal("recorded") }).strict();
1730
+ var observeRunKeptSchema = z12.object({ outcome: z12.literal("kept") }).strict();
1731
+ var observeRunNotSentSchema = z12.object({
1732
+ outcome: z12.literal("not-sent"),
1733
+ reason: z12.enum(OBSERVE_NOT_SENT_REASONS)
1734
+ }).strict();
1735
+ var observeRunResultSchema = z12.discriminatedUnion("outcome", [
1736
+ observeRunRecordedSchema,
1737
+ observeRunKeptSchema,
1738
+ observeRunNotSentSchema
1739
+ ]);
1740
+ var observedChecksFiredSchema = z12.array(z12.string().min(1)).refine((names) => names.every((name, i) => i === 0 || names[i - 1] < name), {
1741
+ message: "must be sorted, each name once"
1742
+ });
1743
+ var observedCountingSchema = z12.discriminatedUnion("state", [
1744
+ z12.object({
1745
+ state: z12.literal("counted"),
1746
+ startCommit: closeCommitSchema,
1747
+ startRecordedAt: utcIso8601,
1748
+ headCommit: closeCommitSchema,
1749
+ headCommittedAt: utcIso8601,
1750
+ commits: observedCountSchema,
1751
+ countedAt: utcIso8601
1752
+ }).strict(),
1753
+ z12.object({
1754
+ state: z12.literal("not-counted-yet"),
1755
+ startCommit: closeCommitSchema,
1756
+ startRecordedAt: utcIso8601
1757
+ }).strict(),
1758
+ z12.object({
1759
+ state: z12.literal("cannot-count")
1760
+ }).strict()
1761
+ ]);
1762
+ var observedPartSchema = z12.object({
1763
+ part: systemPartIdSchema,
1764
+ files: observedFilesSchema.nullable(),
1765
+ checksFired: observedChecksFiredSchema
1766
+ }).strict().refine((v) => v.files !== null && filesChanged(v.files) >= 1 || v.checksFired.length >= 1, {
1767
+ message: "a part is listed only when files changed under it or a check fired on one",
1768
+ path: ["files"]
1769
+ });
1770
+ var observedOutsideSchema = z12.object({
1771
+ files: observedFilesSchema.nullable(),
1772
+ paths: observedPathListSchema,
1773
+ checksFired: observedChecksFiredSchema
1774
+ }).strict().refine((v) => pathsFitTheCount(v.files, v.paths), {
1775
+ message: "lists more paths than files changed",
1776
+ path: ["paths"]
1777
+ });
1778
+ var observedChangesSchema = z12.object({
1779
+ phase: phaseNameSchema,
1780
+ counting: observedCountingSchema,
1781
+ descriptionVersion: observedDescriptionVersionSchema.nullable(),
1782
+ parts: z12.array(observedPartSchema).max(SYSTEM_PARTS_MAX),
1783
+ methodFiles: observedMethodFilesSchema.nullable(),
1784
+ outsideEveryPart: observedOutsideSchema,
1785
+ codeLink: codeLinkFactsSchema.nullable()
1786
+ }).strict().superRefine((v, ctx) => {
1787
+ checkPartsUnique(v.parts, ctx, ["parts"]);
1788
+ const counted2 = v.counting.state === "counted";
1789
+ const rule = counted2 ? "must be present when the phase is counted" : "must be null until the phase is counted";
1790
+ if (v.methodFiles !== null !== counted2)
1791
+ ctx.addIssue({ code: "custom", path: ["methodFiles"], message: rule });
1792
+ if (v.outsideEveryPart.files !== null !== counted2) {
1793
+ ctx.addIssue({ code: "custom", path: ["outsideEveryPart", "files"], message: rule });
1794
+ }
1795
+ if (v.codeLink !== null !== counted2)
1796
+ ctx.addIssue({ code: "custom", path: ["codeLink"], message: rule });
1797
+ v.parts.forEach((entry, i) => {
1798
+ if (entry.files !== null !== counted2)
1799
+ ctx.addIssue({ code: "custom", path: ["parts", i, "files"], message: rule });
1800
+ });
1801
+ if (counted2 && v.descriptionVersion === null) {
1802
+ ctx.addIssue({ code: "custom", path: ["descriptionVersion"], message: "must name the version the counts used" });
1803
+ }
1804
+ });
1805
+ var partOutsideThePlanSchema = z12.object({
1806
+ part: systemPartIdSchema,
1807
+ name: systemPartSchema.shape.name,
1808
+ seen: observedFilesSchema.nullable(),
1809
+ tasks: z12.array(observedSaidSchema.extend({ id: buildTaskSchema.shape.id }).strict()).max(BUILD_SNAPSHOT_TASK_LIMIT)
1810
+ }).strict();
1811
+ function shortCommit(commit) {
1812
+ return commit.slice(0, 7);
1813
+ }
1814
+
1815
+ // ../events/dist/phase-review.js
1816
+ import { z as z13 } from "zod";
1817
+ var BUILT_PART_ACTS = Object.freeze(["accept", "question", "send-back"]);
1818
+ var NOT_BUILT_PART_ACTS = Object.freeze(["carry", "drop"]);
1819
+ var REVIEW_ACTS = Object.freeze([...BUILT_PART_ACTS, ...NOT_BUILT_PART_ACTS]);
1820
+ var reviewActSchema = z13.enum(REVIEW_ACTS);
1821
+ function reviewActsFor(built) {
1822
+ return built ? BUILT_PART_ACTS : NOT_BUILT_PART_ACTS;
1823
+ }
1824
+ var REVIEW_NOTE_MAX = SCOPE_NOTE_MAX;
1825
+ var reviewNoteWireSchema = z13.string().max(REVIEW_NOTE_MAX);
1826
+ var reviewNoteSchema = z13.string().min(1).max(REVIEW_NOTE_MAX);
1827
+ var REVIEW_ANSWER_MAX = 1e3;
1828
+ var reviewAgentAnswerSchema = z13.string().min(1).max(REVIEW_ANSWER_MAX);
1829
+ var REVIEW_PARTS_MAX = PHASE_PLAN_CHANGES_MAX * 2;
1830
+ var REVIEW_PART_CHANGES = Object.freeze([...PHASE_PLAN_CHANGE_KINDS, "not-in-plan"]);
1831
+ var reviewPartChangeSchema = z13.enum(REVIEW_PART_CHANGES);
1832
+ var REVIEW_DECISIONS = Object.freeze(["accepted", "carried", "dropped"]);
1833
+ var reviewDecisionSchema = z13.enum(REVIEW_DECISIONS);
1834
+ function actOf(decision) {
1835
+ if (decision === "accepted")
1836
+ return "accept";
1837
+ if (decision === "carried")
1838
+ return "carry";
1839
+ return "drop";
1840
+ }
1841
+ var reviewCarriedToSchema = carriedPhaseSchema.nullable();
1842
+ var reviewStepNameSchema = stepNameSchema.refine((v) => !stepIdSchema.safeParse(v.trim()).success, {
1843
+ message: "must be the step's plain name"
1844
+ });
1845
+ var reviewStepSchema = z13.object({
1846
+ name: reviewStepNameSchema.nullable(),
1847
+ state: unfinishedStepSchema.shape.state,
1848
+ judged: z13.boolean()
1849
+ }).strict();
1850
+ function codeLinkLine(link) {
1851
+ switch (link.kind) {
1852
+ case "github":
1853
+ 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.`;
1854
+ case "range":
1855
+ 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.`;
1856
+ case "none":
1857
+ return void 0;
1858
+ default:
1859
+ return assertNever(link);
1860
+ }
1861
+ }
1862
+ function assertNever(value) {
1863
+ throw new Error(`unexpected value: ${JSON.stringify(value)}`);
1864
+ }
1865
+ var openReviewRequestSchema = z13.object({
1866
+ actor: closeActorSchema,
1867
+ acceptanceOutcome: acceptanceOutcomeSchema,
1868
+ statements: closeStatementsSchema
1869
+ }).strict();
1870
+ var REVIEW_OPEN_TASKS_SHOWN_MAX = 10;
1871
+ var reviewOpenedSchema = z13.object({
1872
+ outcome: z13.literal("opened"),
1873
+ round: z13.number().int().min(1),
1874
+ parts: z13.number().int().min(0).max(REVIEW_PARTS_MAX),
1875
+ notBuilt: z13.number().int().min(0).max(REVIEW_PARTS_MAX),
1876
+ counted: z13.boolean(),
1877
+ typedReviewOffered: z13.boolean()
1878
+ }).strict().refine((v) => v.notBuilt <= v.parts, { message: "cannot exceed the parts in the round", path: ["notBuilt"] });
1879
+ var reviewTasksOpenSchema = z13.object({
1880
+ outcome: z13.literal("tasks-open"),
1881
+ open: z13.array(buildTaskSchema.shape.title).min(1).max(REVIEW_OPEN_TASKS_SHOWN_MAX),
1882
+ more: z13.number().int().min(0).max(BUILD_SNAPSHOT_TASK_LIMIT)
1883
+ }).strict();
1884
+ var reviewOutstandingSchema = z13.object({
1885
+ outcome: z13.literal("outstanding"),
1886
+ steps: z13.array(reviewStepSchema).min(1)
1887
+ }).strict();
1888
+ var reviewCleanOverFindingsSchema = z13.object({
1889
+ outcome: z13.literal("clean-over-findings"),
1890
+ findings: z13.number().int().min(1)
1891
+ }).strict();
1892
+ var reviewRefusedSchema = z13.object({
1893
+ outcome: z13.literal("refused"),
1894
+ problems: z13.array(systemDescriptionProblemSchema).min(1)
1895
+ }).strict();
1896
+ function bareReviewOutcome(outcome) {
1897
+ return z13.object({ outcome: z13.literal(outcome) }).strict();
1898
+ }
1899
+ var openReviewResultSchema = z13.discriminatedUnion("outcome", [
1900
+ reviewOpenedSchema,
1901
+ reviewTasksOpenSchema,
1902
+ bareReviewOutcome("no-build"),
1903
+ bareReviewOutcome("review-open"),
1904
+ reviewOutstandingSchema,
1905
+ reviewCleanOverFindingsSchema,
1906
+ reviewRefusedSchema,
1907
+ bareReviewOutcome("unavailable")
1908
+ ]);
1909
+ var reviewPhaseNotOpenSchema = z13.object({
1910
+ statusCode: z13.literal(409),
1911
+ error: z13.literal("PhaseNotOpen"),
1912
+ message: z13.string().min(1),
1913
+ openPhase: z13.string().nullable()
1914
+ }).strict();
1915
+ var openReviewAnswerSchema = z13.union([openReviewResultSchema, reviewPhaseNotOpenSchema]);
1916
+ var reviewDecisionEntrySchema = z13.object({
1917
+ part: systemPartIdSchema,
1918
+ act: reviewActSchema,
1919
+ note: reviewNoteWireSchema.optional()
1920
+ }).strict();
1921
+ var reviewDecisionRequestSchema = z13.object({
1922
+ reviewVersion: z13.number().int().min(0),
1923
+ decisions: z13.array(reviewDecisionEntrySchema).max(REVIEW_PARTS_MAX)
1924
+ }).strict();
1925
+ var reviewDecidedSchema = z13.object({
1926
+ part: systemPartIdSchema,
1927
+ name: systemPartSchema.shape.name,
1928
+ act: reviewActSchema,
1929
+ carriedTo: reviewCarriedToSchema.optional()
1930
+ }).strict().refine((v) => v.carriedTo !== void 0 === (v.act === "carry"), {
1931
+ message: "is present exactly on a carry",
1932
+ path: ["carriedTo"]
1933
+ });
1934
+ var reviewRecordedSchema = z13.object({
1935
+ outcome: z13.literal("recorded"),
1936
+ decided: z13.array(reviewDecidedSchema).max(REVIEW_PARTS_MAX),
1937
+ waiting: z13.number().int().min(0).max(REVIEW_PARTS_MAX),
1938
+ closed: z13.boolean(),
1939
+ reviewVersion: z13.number().int().min(1)
1940
+ }).strict().superRefine((v, ctx) => {
1941
+ if (v.closed && v.waiting !== 0) {
1942
+ ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be 0 once the phase is closed" });
1943
+ }
1944
+ if (v.decided.length === 0 && !v.closed) {
1945
+ ctx.addIssue({ code: "custom", path: ["decided"], message: "may be empty only when the phase closed" });
1946
+ }
1947
+ });
1948
+ var REVIEW_NOT_DECIDABLE_WHY = Object.freeze(["settled", "question-waiting", "sent-back"]);
1949
+ var reviewNotDecidablePartSchema = z13.object({
1950
+ part: systemPartIdSchema,
1951
+ name: systemPartSchema.shape.name,
1952
+ why: z13.enum(REVIEW_NOT_DECIDABLE_WHY)
1953
+ }).strict();
1954
+ var reviewNotDecidableSchema = z13.object({
1955
+ outcome: z13.literal("not-decidable"),
1956
+ parts: z13.array(reviewNotDecidablePartSchema).min(1).max(REVIEW_PARTS_MAX)
1957
+ }).strict();
1958
+ var reviewCloseRefusedSchema = z13.object({
1959
+ outcome: z13.literal("close-refused"),
1960
+ steps: z13.array(reviewStepSchema)
1961
+ }).strict();
1962
+ var reviewWalkChangedSchema = z13.object({
1963
+ outcome: z13.literal("walk-changed"),
1964
+ findings: z13.number().int().min(1)
1965
+ }).strict();
1966
+ var reviewDecisionResultSchema = z13.discriminatedUnion("outcome", [
1967
+ reviewRecordedSchema,
1968
+ bareReviewOutcome("review-changed"),
1969
+ bareReviewOutcome("no-review"),
1970
+ reviewNotDecidableSchema,
1971
+ reviewCloseRefusedSchema,
1972
+ reviewWalkChangedSchema,
1973
+ bareReviewOutcome("not-owner"),
1974
+ bareReviewOutcome("no-owner"),
1975
+ reviewRefusedSchema,
1976
+ bareReviewOutcome("unavailable")
1977
+ ]);
1978
+ var reviewSentBackSchema = z13.object({
1979
+ round: z13.number().int().min(1),
1980
+ note: reviewNoteSchema
1981
+ }).strict();
1982
+ var reviewQuestionSchema = z13.object({
1983
+ question: reviewNoteSchema,
1984
+ agentAnswer: reviewAgentAnswerSchema.optional()
1985
+ }).strict();
1986
+ var reviewTaskSchema = z13.object({
1987
+ title: buildTaskSchema.shape.title,
1988
+ state: buildTaskStateSchema
1989
+ }).strict();
1990
+ var reviewViewPartSchema = z13.object({
1991
+ part: systemPartIdSchema,
1992
+ name: systemPartSchema.shape.name,
1993
+ change: reviewPartChangeSchema,
1994
+ built: z13.boolean(),
1995
+ builtWithoutApproval: z13.boolean(),
1996
+ decidable: z13.boolean(),
1997
+ waiting: z13.boolean(),
1998
+ decision: reviewDecisionSchema.optional(),
1999
+ reason: reviewNoteSchema.optional(),
2000
+ carriedTo: reviewCarriedToSchema.optional(),
2001
+ sentBack: z13.array(reviewSentBackSchema),
2002
+ questions: z13.array(reviewQuestionSchema),
2003
+ tasks: z13.array(reviewTaskSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
2004
+ said: z13.array(observedSaidSchema).max(BUILD_SNAPSHOT_TASK_LIMIT),
2005
+ codeLink: partCodeLinkSchema
2006
+ }).strict().superRefine((v, ctx) => {
2007
+ if (v.waiting !== (v.decision === void 0)) {
2008
+ ctx.addIssue({ code: "custom", path: ["waiting"], message: "must be true exactly when the part has no decision" });
2009
+ }
2010
+ if (v.decidable && !v.waiting) {
2011
+ ctx.addIssue({ code: "custom", path: ["decidable"], message: "cannot be true for a settled part" });
2012
+ }
2013
+ if (v.decision !== void 0 && !reviewActsFor(v.built).includes(actOf(v.decision))) {
2014
+ ctx.addIssue({ code: "custom", path: ["decision"], message: "is not an act this part takes" });
2015
+ }
2016
+ const carriedOrDropped = v.decision === "carried" || v.decision === "dropped";
2017
+ if (v.reason !== void 0 !== carriedOrDropped) {
2018
+ ctx.addIssue({ code: "custom", path: ["reason"], message: "is present exactly on a carry or a drop" });
2019
+ }
2020
+ if (v.carriedTo !== void 0 !== (v.decision === "carried")) {
2021
+ ctx.addIssue({ code: "custom", path: ["carriedTo"], message: "is present exactly on a carry" });
2022
+ }
2023
+ if (v.change === "not-in-plan" && !v.built) {
2024
+ ctx.addIssue({ code: "custom", path: ["built"], message: "is always true for a part outside the plan" });
2025
+ }
2026
+ });
2027
+ var reviewPhaseNumberSchema = z13.number().int().min(1).nullable();
2028
+ var phaseReviewNoneSchema = z13.object({
2029
+ state: z13.literal("none"),
2030
+ phase: scopePhaseSchema,
2031
+ number: reviewPhaseNumberSchema,
2032
+ closedWithoutReview: z13.boolean()
2033
+ }).strict();
2034
+ var reviewWalkSchema = z13.object({
2035
+ verdict: walkVerdictSchema,
2036
+ findings: z13.number().int().min(0)
2037
+ }).strict();
2038
+ function reviewedViewSchema(state) {
2039
+ return z13.object({
2040
+ state: z13.literal(state),
2041
+ phase: scopePhaseSchema,
2042
+ number: reviewPhaseNumberSchema,
2043
+ cycle: z13.number().int().min(0),
2044
+ round: z13.number().int().min(1),
2045
+ reviewVersion: z13.number().int().min(1),
2046
+ typedReviewOffered: z13.boolean(),
2047
+ counted: z13.boolean(),
2048
+ walk: reviewWalkSchema,
2049
+ parts: z13.array(reviewViewPartSchema).max(REVIEW_PARTS_MAX),
2050
+ observed: observedChangesSchema.nullable(),
2051
+ description: systemDescriptionSchema
2052
+ }).strict().superRefine((v, ctx) => {
2053
+ const seen = /* @__PURE__ */ new Set();
2054
+ v.parts.forEach((p, i) => {
2055
+ if (seen.has(p.part))
2056
+ ctx.addIssue({ code: "custom", path: ["parts", i, "part"], message: "is listed twice" });
2057
+ seen.add(p.part);
2058
+ });
2059
+ const counted2 = v.observed !== null && v.observed.counting.state === "counted";
2060
+ if (v.counted !== counted2) {
2061
+ ctx.addIssue({ code: "custom", path: ["counted"], message: "must be true exactly when the counts are counted" });
2062
+ }
2063
+ if (v.observed !== null && v.observed.phase !== v.phase) {
2064
+ ctx.addIssue({ code: "custom", path: ["observed", "phase"], message: "must be the phase under review" });
2065
+ }
2066
+ if (v.state === "closed") {
2067
+ v.parts.forEach((p, i) => {
2068
+ if (p.decision === void 0) {
2069
+ ctx.addIssue({ code: "custom", path: ["parts", i, "decision"], message: "every part of a closed review is decided" });
2070
+ }
2071
+ });
2072
+ }
2073
+ });
2074
+ }
2075
+ var phaseReviewOpenSchema = reviewedViewSchema("open");
2076
+ var phaseReviewClosedSchema = reviewedViewSchema("closed");
2077
+ var phaseReviewViewSchema = z13.discriminatedUnion("state", [
2078
+ phaseReviewNoneSchema,
2079
+ phaseReviewOpenSchema,
2080
+ phaseReviewClosedSchema
2081
+ ]);
2082
+
2083
+ // ../events/dist/build-record-v3.js
2084
+ var BUILD_RECORD_V3_FORMAT = "halfcycle-build-record/v3";
2085
+ var recordReviewRoundSchema = z14.object({
2086
+ round: z14.number().int().min(1),
2087
+ openedAt: utcIso8601,
2088
+ verdict: walkVerdictSchema,
2089
+ findings: z14.number().int().min(0)
2090
+ }).strict();
2091
+ var recordReviewApprovalSchema = z14.discriminatedUnion("kind", [
2092
+ z14.object({
2093
+ kind: z14.literal("approved"),
2094
+ planVersion: planVersionSchema,
2095
+ givenIn: scopeGivenInSchema,
2096
+ approvedAt: utcIso8601
2097
+ }).strict(),
2098
+ z14.object({ kind: z14.literal("none") }).strict()
2099
+ ]);
2100
+ var recordReviewGateSchema = z14.object({
2101
+ gate: boardGateNameSchema,
2102
+ state: boardStepStateSchema
2103
+ }).strict();
2104
+ var recordReviewQuestionSchema = z14.object({
2105
+ question: reviewNoteSchema,
2106
+ agentAnswer: reviewAgentAnswerSchema
2107
+ }).strict();
2108
+ var recordReviewPartSchema = z14.object({
2109
+ part: systemPartIdSchema,
2110
+ name: systemPartSchema.shape.name,
2111
+ change: reviewPartChangeSchema,
2112
+ built: z14.boolean(),
2113
+ builtWithoutApproval: z14.boolean(),
2114
+ decision: reviewDecisionSchema,
2115
+ reason: reviewNoteSchema.optional(),
2116
+ carriedTo: reviewCarriedToSchema.optional(),
2117
+ sentBack: z14.array(reviewSentBackSchema),
2118
+ questions: z14.array(recordReviewQuestionSchema),
2119
+ codeLink: partCodeLinkSchema
2120
+ }).strict().superRefine((v, ctx) => {
2121
+ const carriedOrDropped = v.decision === "carried" || v.decision === "dropped";
2122
+ if (v.reason !== void 0 !== carriedOrDropped) {
2123
+ ctx.addIssue({ code: "custom", path: ["reason"], message: "is present exactly on a carry or a drop" });
2124
+ }
2125
+ if (v.carriedTo !== void 0 !== (v.decision === "carried")) {
2126
+ ctx.addIssue({ code: "custom", path: ["carriedTo"], message: "is present exactly on a carry" });
2127
+ }
2128
+ });
2129
+ var SPINE_ORDER = boardGateNameSchema.options;
2130
+ var recordReviewGatesSchema = z14.array(recordReviewGateSchema).superRefine((gates, ctx) => {
2131
+ const inOrder = gates.length === SPINE_ORDER.length && gates.every((g, i) => g.gate === SPINE_ORDER[i]);
2132
+ if (!inOrder) {
2133
+ ctx.addIssue({ code: "custom", message: `must be exactly the five checkpoints in order: ${SPINE_ORDER.join(", ")}` });
2134
+ }
2135
+ });
2136
+ var recordReviewSchema = z14.object({
2137
+ sequence: z14.number().int().min(1),
2138
+ cycle: z14.number().int().min(0),
2139
+ rounds: z14.array(recordReviewRoundSchema).min(1),
2140
+ approval: recordReviewApprovalSchema,
2141
+ planVersion: planVersionSchema.nullable(),
2142
+ descriptionVersion: observedDescriptionVersionSchema,
2143
+ gates: recordReviewGatesSchema,
2144
+ observed: observedChangesSchema.nullable(),
2145
+ parts: z14.array(recordReviewPartSchema).max(REVIEW_PARTS_MAX)
2146
+ }).strict().superRefine((v, ctx) => {
2147
+ v.rounds.forEach((r, i) => {
2148
+ if (r.round !== i + 1) {
2149
+ ctx.addIssue({ code: "custom", path: ["rounds", i, "round"], message: "rounds must be numbered 1, 2, 3 \u2026 in order with no gap" });
2150
+ }
2151
+ });
2152
+ const seen = /* @__PURE__ */ new Set();
2153
+ v.parts.forEach((p, i) => {
2154
+ if (seen.has(p.part))
2155
+ ctx.addIssue({ code: "custom", path: ["parts", i, "part"], message: "is listed twice" });
2156
+ seen.add(p.part);
2157
+ if (v.observed === null && p.codeLink.kind !== "none") {
2158
+ ctx.addIssue({ code: "custom", path: ["parts", i, "codeLink"], message: "must be none when nothing was observed" });
2159
+ }
2160
+ });
2161
+ });
2162
+ var buildRecordV3Schema = z14.object({
2163
+ ...buildRecordV2Schema.shape,
2164
+ format: z14.literal(BUILD_RECORD_V3_FORMAT),
2165
+ review: recordReviewSchema
2166
+ }).strict().superRefine((record2, ctx) => {
2167
+ checkRecordHalves(record2, ctx);
2168
+ const closes = record2.observed.closes;
2169
+ const newest = closes[closes.length - 1].sequence;
2170
+ if (record2.review.sequence !== newest) {
2171
+ ctx.addIssue({ code: "custom", path: ["review", "sequence"], message: "must be the newest close" });
2172
+ }
2173
+ const at = record2.attested.closes.findIndex((c) => c.sequence === record2.review.sequence);
2174
+ const close = at === -1 ? void 0 : record2.attested.closes[at];
2175
+ if (close !== void 0 && close.decision !== "done-when-met") {
2176
+ ctx.addIssue({ code: "custom", path: ["attested", "closes", at, "decision"], message: "a close made by a review is done-when-met" });
2177
+ }
2178
+ const last = record2.review.rounds[record2.review.rounds.length - 1];
2179
+ if (close !== void 0 && last !== void 0) {
2180
+ const attested = close.walk === null ? null : close.walk.findings.length + close.walk.findingsNotShown;
2181
+ if (attested !== last.findings) {
2182
+ ctx.addIssue({
2183
+ code: "custom",
2184
+ path: ["review", "rounds", record2.review.rounds.length - 1, "findings"],
2185
+ message: "must equal the findings of the closing walk"
2186
+ });
2187
+ }
2188
+ }
2189
+ if (record2.review.observed !== null && record2.review.observed.phase !== record2.phase) {
2190
+ ctx.addIssue({ code: "custom", path: ["review", "observed", "phase"], message: "must be this record's phase" });
2191
+ }
2192
+ });
2193
+ var buildRecordSchema = z14.discriminatedUnion("format", [buildRecordV2Schema, buildRecordV3Schema]);
2194
+ function removalsOf(record2) {
2195
+ if (record2.format !== BUILD_RECORD_V3_FORMAT)
2196
+ return [];
2197
+ const out = [];
2198
+ for (const p of record2.review.parts) {
2199
+ if (p.change === "add" && p.decision === "dropped")
2200
+ out.push({ part: p.part, name: p.name, act: "dropped" });
2201
+ else if (p.change === "remove" && p.decision === "accepted")
2202
+ out.push({ part: p.part, name: p.name, act: "removed" });
2203
+ }
2204
+ return out;
2205
+ }
933
2206
 
934
2207
  // ../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");
2208
+ import { z as z15 } from "zod";
2209
+ 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
2210
  var DEVICE_AUTH_STATUS = {
938
2211
  /** The poll: minted, nobody has acted yet. Keep polling. 200. */
939
2212
  PENDING: "pending",
@@ -974,25 +2247,25 @@ var DEVICE_POLL_REFUSALS = [
974
2247
  DEVICE_AUTH_STATUS.EXPIRED,
975
2248
  DEVICE_AUTH_STATUS.UNKNOWN
976
2249
  ];
977
- var deviceAuthStartResponseSchema = z6.object({
978
- deviceCode: z6.string().min(1),
979
- userCode: z6.string().min(1),
980
- verificationUrl: z6.string().min(1),
2250
+ var deviceAuthStartResponseSchema = z15.object({
2251
+ deviceCode: z15.string().min(1),
2252
+ userCode: z15.string().min(1),
2253
+ verificationUrl: z15.string().min(1),
981
2254
  expiresAt: utcIso86012,
982
- pollIntervalMs: z6.number().int().positive()
2255
+ pollIntervalMs: z15.number().int().positive()
983
2256
  }).strict();
984
- var devicePollPendingSchema = z6.object({
985
- status: z6.literal(DEVICE_AUTH_STATUS.PENDING),
986
- pollIntervalMs: z6.number().int().positive()
2257
+ var devicePollPendingSchema = z15.object({
2258
+ status: z15.literal(DEVICE_AUTH_STATUS.PENDING),
2259
+ pollIntervalMs: z15.number().int().positive()
987
2260
  }).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),
2261
+ var devicePollApprovedSchema = z15.object({
2262
+ status: z15.literal(DEVICE_AUTH_STATUS.APPROVED),
2263
+ accountId: z15.string().min(1),
2264
+ credential: z15.string().min(1),
992
2265
  expiresAt: utcIso86012
993
2266
  }).strict();
994
- var deviceAuthRefusalSchema = z6.object({
995
- status: z6.enum([
2267
+ var deviceAuthRefusalSchema = z15.object({
2268
+ status: z15.enum([
996
2269
  DEVICE_AUTH_STATUS.DECLINED,
997
2270
  DEVICE_AUTH_STATUS.EXPIRED,
998
2271
  DEVICE_AUTH_STATUS.UNKNOWN,
@@ -1003,25 +2276,25 @@ var deviceAuthRefusalSchema = z6.object({
1003
2276
  DEVICE_AUTH_STATUS.ALREADY_DECIDED,
1004
2277
  DEVICE_AUTH_STATUS.TERMS_REQUIRED
1005
2278
  ]),
1006
- message: z6.string().min(1)
2279
+ message: z15.string().min(1)
1007
2280
  }).strict();
1008
- var devicePollResponseSchema = z6.union([
2281
+ var devicePollResponseSchema = z15.union([
1009
2282
  devicePollPendingSchema,
1010
2283
  devicePollApprovedSchema,
1011
2284
  deviceAuthRefusalSchema
1012
2285
  ]);
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()
2286
+ var deviceConfirmRequestSchema = z15.object({
2287
+ userCode: z15.string().min(1),
2288
+ decision: z15.enum(["approve", "decline"]),
2289
+ externalAuthId: z15.string().min(1).optional(),
2290
+ email: z15.string().optional(),
2291
+ acceptTerms: z15.boolean().optional()
1019
2292
  }).strict();
1020
- var deviceConfirmRecordedSchema = z6.object({
1021
- status: z6.literal(DEVICE_AUTH_STATUS.RECORDED),
1022
- accountId: z6.string().min(1).optional()
2293
+ var deviceConfirmRecordedSchema = z15.object({
2294
+ status: z15.literal(DEVICE_AUTH_STATUS.RECORDED),
2295
+ accountId: z15.string().min(1).optional()
1023
2296
  }).strict();
1024
- var deviceConfirmResponseSchema = z6.union([
2297
+ var deviceConfirmResponseSchema = z15.union([
1025
2298
  deviceConfirmRecordedSchema,
1026
2299
  deviceAuthRefusalSchema
1027
2300
  ]);
@@ -1033,20 +2306,20 @@ function projectPageUrl(engagementId) {
1033
2306
  }
1034
2307
 
1035
2308
  // ../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()
2309
+ import { z as z16 } from "zod";
2310
+ var engagementCredentialResponseSchema = z16.object({
2311
+ engagementId: z16.string(),
2312
+ sessionToken: z16.string(),
2313
+ mcpUrl: z16.string(),
2314
+ guardUrl: z16.string(),
2315
+ controlTelemetryUrl: z16.string(),
2316
+ methodVersion: z16.string().optional(),
2317
+ consoleUrl: z16.string().optional()
1045
2318
  }).strict();
1046
- var ciTokenExchangeResponseSchema = z7.object({
1047
- token: z7.string(),
2319
+ var ciTokenExchangeResponseSchema = z16.object({
2320
+ token: z16.string(),
1048
2321
  expiresAt: utcIso8601,
1049
- engagementId: z7.string()
2322
+ engagementId: z16.string()
1050
2323
  }).strict();
1051
2324
  var NO_CREDENTIAL_PLACEHOLDER = "halfcycle:no-credential";
1052
2325
  var NOT_THIS_ACCOUNT_PLACEHOLDER = "halfcycle:not-this-account";
@@ -1063,37 +2336,37 @@ var PLACEHOLDER_SENTENCES = Object.freeze({
1063
2336
  });
1064
2337
 
1065
2338
  // ../events/dist/phase-record.js
1066
- import { z as z8 } from "zod";
1067
- var phaseRecordRefusalReasonSchema = z8.enum([
2339
+ import { z as z17 } from "zod";
2340
+ var phaseRecordRefusalReasonSchema = z17.enum([
1068
2341
  "still-open",
1069
2342
  "reopened",
1070
2343
  "never-opened",
1071
2344
  "never-closed",
1072
2345
  "closed-before-records"
1073
2346
  ]);
1074
- var phaseRecordRecordSchema = z8.object({
1075
- outcome: z8.literal("record"),
2347
+ var phaseRecordRecordSchema = z17.object({
2348
+ outcome: z17.literal("record"),
1076
2349
  phase: phaseNameSchema,
1077
- record: buildRecordV2Schema
2350
+ record: buildRecordSchema
1078
2351
  }).strict().refine((body) => body.record.phase === body.phase, {
1079
2352
  message: "the record is for a different phase than the one asked for",
1080
2353
  path: ["record", "phase"]
1081
2354
  });
1082
- var phaseRecordWithheldSchema = z8.object({
1083
- outcome: z8.literal("withheld"),
2355
+ var phaseRecordWithheldSchema = z17.object({
2356
+ outcome: z17.literal("withheld"),
1084
2357
  phase: phaseNameSchema,
1085
- sequence: z8.number().int().min(1),
2358
+ sequence: z17.number().int().min(1),
1086
2359
  closedAt: utcIso8601,
1087
- message: z8.string()
2360
+ message: z17.string()
1088
2361
  }).strict();
1089
2362
  var phaseRecordRefusedSchema = wireErrorSchema.extend({
1090
- outcome: z8.literal("refused"),
2363
+ outcome: z17.literal("refused"),
1091
2364
  phase: phaseNameSchema,
1092
- statusCode: z8.literal(409),
1093
- error: z8.literal("PhaseRecordRefused"),
2365
+ statusCode: z17.literal(409),
2366
+ error: z17.literal("PhaseRecordRefused"),
1094
2367
  reason: phaseRecordRefusalReasonSchema
1095
2368
  }).strict();
1096
- var phaseRecordResponseSchema = z8.discriminatedUnion("outcome", [
2369
+ var phaseRecordResponseSchema = z17.discriminatedUnion("outcome", [
1097
2370
  phaseRecordRecordSchema,
1098
2371
  phaseRecordWithheldSchema,
1099
2372
  phaseRecordRefusedSchema
@@ -1147,6 +2420,7 @@ var ENGAGEMENT_ENV_KEYS = [
1147
2420
  ...GUARD_ENV_KEYS,
1148
2421
  "CONTROL_TELEMETRY_URL"
1149
2422
  ];
2423
+ var PHASE_STAMP_ENV_KEY = "HALFCYCLE_PHASE";
1150
2424
  function reconcileEnvText2(existing, values, keys = ENGAGEMENT_ENV_KEYS) {
1151
2425
  return reconcileEnvText(existing, values, keys);
1152
2426
  }
@@ -1247,7 +2521,7 @@ halfcycle_env_file() {
1247
2521
 
1248
2522
  // dist/identity.js
1249
2523
  import { execFileSync } from "node:child_process";
1250
- import { existsSync, linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2524
+ import { existsSync, linkSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
1251
2525
  import { dirname as dirname2, isAbsolute, join as join3 } from "node:path";
1252
2526
  import { randomUUID } from "node:crypto";
1253
2527
  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 +2570,41 @@ function readRootCommit(targetRepoRoot) {
1296
2570
  function readRemote(targetRepoRoot) {
1297
2571
  return git(targetRepoRoot, ["remote", "get-url", "origin"]);
1298
2572
  }
2573
+ function stripUrlUserinfoTextually(trimmed) {
2574
+ const schemeEnd = trimmed.indexOf("://") + 3;
2575
+ const pathStart = (() => {
2576
+ const match = /[/?#]/.exec(trimmed.slice(schemeEnd));
2577
+ return match ? schemeEnd + match.index : trimmed.length;
2578
+ })();
2579
+ const authority = trimmed.slice(schemeEnd, pathStart);
2580
+ const at = authority.lastIndexOf("@");
2581
+ if (at === -1)
2582
+ return trimmed;
2583
+ return trimmed.slice(0, schemeEnd) + authority.slice(at + 1) + trimmed.slice(pathStart);
2584
+ }
2585
+ function redactRemoteCredential(remote) {
2586
+ if (remote === null)
2587
+ return null;
2588
+ const trimmed = remote.trim();
2589
+ if (trimmed.includes("://")) {
2590
+ try {
2591
+ const url = new URL(trimmed);
2592
+ if (url.username === "" && url.password === "")
2593
+ return trimmed;
2594
+ url.username = "";
2595
+ url.password = "";
2596
+ return url.toString();
2597
+ } catch {
2598
+ return stripUrlUserinfoTextually(trimmed);
2599
+ }
2600
+ }
2601
+ const at = trimmed.lastIndexOf("@");
2602
+ if (at === -1)
2603
+ return trimmed;
2604
+ const userinfo = trimmed.slice(0, at);
2605
+ const rest = trimmed.slice(at + 1);
2606
+ return userinfo.includes(":") && rest.includes(":") ? rest : trimmed;
2607
+ }
1299
2608
  function readIgnoredPaths(targetRepoRoot, paths) {
1300
2609
  if (paths.length === 0)
1301
2610
  return null;
@@ -1331,11 +2640,12 @@ function readIgnoredPaths(targetRepoRoot, paths) {
1331
2640
  function mintOrReadIdentity(targetRepoRoot) {
1332
2641
  const path = join3(targetRepoRoot, ".halfcycle", "project.json");
1333
2642
  if (existsSync(path)) {
2643
+ repairRemoteCredential(targetRepoRoot);
1334
2644
  const onDisk = JSON.parse(readFileSync2(path, "utf-8"));
1335
2645
  const identity = {
1336
2646
  projectId: onDisk.projectId ?? "",
1337
2647
  rootCommit: onDisk.rootCommit ?? null,
1338
- remote: onDisk.remote ?? null,
2648
+ remote: redactRemoteCredential(onDisk.remote ?? null),
1339
2649
  note: PROJECT_IDENTITY_NOTE
1340
2650
  };
1341
2651
  return { identity, minted: false };
@@ -1346,10 +2656,40 @@ function mintIdentity(targetRepoRoot) {
1346
2656
  return {
1347
2657
  projectId: randomUUID(),
1348
2658
  rootCommit: readRootCommit(targetRepoRoot),
1349
- remote: readRemote(targetRepoRoot),
2659
+ remote: redactRemoteCredential(readRemote(targetRepoRoot)),
1350
2660
  note: PROJECT_IDENTITY_NOTE
1351
2661
  };
1352
2662
  }
2663
+ function repairRemoteCredential(targetRepoRoot) {
2664
+ const path = join3(targetRepoRoot, ".halfcycle", "project.json");
2665
+ let onDisk;
2666
+ try {
2667
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
2668
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
2669
+ return;
2670
+ onDisk = parsed;
2671
+ } catch {
2672
+ return;
2673
+ }
2674
+ const storedRemote = onDisk.remote ?? null;
2675
+ const remote = redactRemoteCredential(storedRemote);
2676
+ if (remote === storedRemote)
2677
+ return;
2678
+ const identity = {
2679
+ projectId: onDisk.projectId ?? "",
2680
+ rootCommit: onDisk.rootCommit ?? null,
2681
+ remote,
2682
+ note: PROJECT_IDENTITY_NOTE
2683
+ };
2684
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
2685
+ try {
2686
+ writeFileSync2(temporary, serializeProjectIdentity(identity), { encoding: "utf-8", flag: "wx" });
2687
+ renameSync(temporary, path);
2688
+ } catch {
2689
+ } finally {
2690
+ rmSync(temporary, { force: true });
2691
+ }
2692
+ }
1353
2693
  function serializeProjectIdentity(identity) {
1354
2694
  return JSON.stringify(identity, null, 2) + "\n";
1355
2695
  }
@@ -2399,18 +3739,21 @@ exit 0
2399
3739
  var GENERATED_USER_PROMPT_SCOPE_HEADER = "# Typed scope approval \u2014 generated by";
2400
3740
  function generateUserPromptScopeHook() {
2401
3741
  return `#!/usr/bin/env bash
2402
- ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer.
3742
+ ${GENERATED_USER_PROMPT_SCOPE_HEADER} the Halfcycle installer, now also for the close review.
2403
3743
  #
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.
3744
+ # Lets the owner of this project approve a phase's scope, or send it back, and
3745
+ # decide one part of a phase's close review, by typing one line as the whole
3746
+ # prompt in their own Claude Code session: "approve <phase>", "send back
3747
+ # <phase>: <note>", "accept <phase>/<part>", "question <phase>/<part>: <words>",
3748
+ # "carry <phase>/<part>: <words>" or "drop <phase>/<part>: <words>".
2407
3749
  #
2408
3750
  # AN ORDINARY PROMPT STARTS NOTHING. This script reads the hook input once and
2409
3751
  # 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.
3752
+ # any whitespace (typed, or escaped in the input), with "approve ", "send back ",
3753
+ # "accept ", "question ", "carry " or "drop ", in any case, it exits 0 with no
3754
+ # output, and Node never starts. A quote inside any other field arrives escaped,
3755
+ # so no other field (the folder the session is in, for one) can read as the
3756
+ # start of the prompt.
2414
3757
  #
2415
3758
  # Otherwise it finds the project root from its own location, never from the
2416
3759
  # folder the session has moved to, and hands the same input to the guard
@@ -2431,7 +3774,7 @@ IFS= read -r -d '' HC_INPUT || true
2431
3774
  # \\u escape, or as the UTF-8 bytes of a Unicode space. Compared byte for byte.
2432
3775
  HC_LEAD='[[:space:]]|\\\\[nrtf]|\\\\u(000[9a-d]|0020|00a0|1680|200[0-9a]|202[89f]|205f|3000|feff)'
2433
3776
  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
3777
+ if ! printf '%s' "$HC_INPUT" | LC_ALL=C grep -Eiq "\\"prompt\\"[[:space:]]*:[[:space:]]*\\"($HC_LEAD)*(approve|send back|accept|question|carry|drop) "; then
2435
3778
  exit 0
2436
3779
  fi
2437
3780
 
@@ -2711,8 +4054,10 @@ function checkDrift(targetRepoRoot) {
2711
4054
  return { drifted: installed !== current, installed, current, methodVersion: pin?.methodVersion ?? null };
2712
4055
  }
2713
4056
  function writeProjectIdentity(targetRepo) {
2714
- if (existsSync3(join5(targetRepo, PROJECT_IDENTITY_REL)))
4057
+ if (existsSync3(join5(targetRepo, PROJECT_IDENTITY_REL))) {
4058
+ repairRemoteCredential(targetRepo);
2715
4059
  return "skipped";
4060
+ }
2716
4061
  createProjectIdentityFile(targetRepo);
2717
4062
  return "written";
2718
4063
  }
@@ -3682,6 +5027,32 @@ function printRecordText(text) {
3682
5027
  });
3683
5028
  return out;
3684
5029
  }
5030
+ var MARKUP = /[\\`*_~$<>&[\]{}#|]/;
5031
+ function printStrictText(text) {
5032
+ if (text === "")
5033
+ return "(blank)";
5034
+ let out = "";
5035
+ for (const ch of text) {
5036
+ if (HIDDEN.test(ch))
5037
+ out += `U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`;
5038
+ else if (MARKUP.test(ch))
5039
+ out += `\\${ch}`;
5040
+ else
5041
+ out += ch;
5042
+ }
5043
+ return out;
5044
+ }
5045
+ function printItemStartText(text) {
5046
+ const out = printStrictText(text);
5047
+ if (out.startsWith(" "))
5048
+ return `&#32;${out.slice(1)}`;
5049
+ if (out.startsWith("-") || out.startsWith("+"))
5050
+ return `\\${out}`;
5051
+ const ordered = /^(\d{1,9})([.)])/.exec(out);
5052
+ if (ordered !== null)
5053
+ return `${ordered[1]}\\${out.slice(ordered[1].length)}`;
5054
+ return out;
5055
+ }
3685
5056
  function makePrinter() {
3686
5057
  let hidden = false;
3687
5058
  return {
@@ -3690,6 +5061,16 @@ function makePrinter() {
3690
5061
  hidden = true;
3691
5062
  return printRecordText(value);
3692
5063
  },
5064
+ strict(value) {
5065
+ if (!hidden && HIDDEN.test(value))
5066
+ hidden = true;
5067
+ return printStrictText(value);
5068
+ },
5069
+ itemStart(value) {
5070
+ if (!hidden && HIDDEN.test(value))
5071
+ hidden = true;
5072
+ return printItemStartText(value);
5073
+ },
3693
5074
  sawHidden() {
3694
5075
  return hidden;
3695
5076
  }
@@ -3905,6 +5286,52 @@ function renderClose(heading, observed, attested, checks, p) {
3905
5286
  renderStated(attested, p)
3906
5287
  ];
3907
5288
  }
5289
+ 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.";
5290
+ var REVIEW_NO_PARTS = "The review had no parts, so it closed on one accept that names no part.";
5291
+ function approvalLine(approval) {
5292
+ if (approval.kind === "none")
5293
+ return "Built without an approved scope.";
5294
+ const where = approval.givenIn === "console" ? "in the console" : "in their own session";
5295
+ return `Scope approved by the project's owner, plan version ${String(approval.planVersion)}, ${where}.`;
5296
+ }
5297
+ function decisionText(part, p) {
5298
+ switch (part.decision) {
5299
+ case "accepted":
5300
+ return "Accepted";
5301
+ case "carried":
5302
+ return part.carriedTo === null || part.carriedTo === void 0 ? `Carried to the next phase planned: ${p.text(part.reason ?? "")}` : `Carried to phase "${part.carriedTo.phase}": ${p.text(part.reason ?? "")}`;
5303
+ case "dropped":
5304
+ return `Dropped: ${p.text(part.reason ?? "")}`;
5305
+ }
5306
+ }
5307
+ function reviewPartLines(part, p) {
5308
+ const lines = [`- ${p.itemStart(part.name)} \u2014 ${decisionText(part, p)}`];
5309
+ const code = codeLinkLine(part.codeLink);
5310
+ if (code !== void 0)
5311
+ lines.push(` - ${p.text(code)}`);
5312
+ for (const sent of part.sentBack)
5313
+ lines.push(` - Sent back in round ${String(sent.round)}: ${p.text(sent.note)}`);
5314
+ for (const q of part.questions) {
5315
+ lines.push(` - Question: ${p.text(q.question)}`);
5316
+ lines.push(` - The agent answered: ${p.text(q.agentAnswer)}`);
5317
+ }
5318
+ return lines;
5319
+ }
5320
+ function removalsLine(record2, p) {
5321
+ const removals = removalsOf(record2);
5322
+ if (removals.length === 0)
5323
+ return null;
5324
+ return `Take these parts out of docs/system.json: ${removals.map((r) => `"${p.strict(r.name)}" (${r.act})`).join(", ")}.`;
5325
+ }
5326
+ function renderReview(record2, p) {
5327
+ const review = record2.review;
5328
+ const blocks = ["## The owner's review", REVIEW_LABEL, approvalLine(review.approval)];
5329
+ blocks.push(review.parts.length === 0 ? REVIEW_NO_PARTS : review.parts.flatMap((part) => reviewPartLines(part, p)).join("\n"));
5330
+ const removals = removalsLine(record2, p);
5331
+ if (removals !== null)
5332
+ blocks.push(removals);
5333
+ return blocks;
5334
+ }
3908
5335
  function renderBuildRecordV2Markdown(record2) {
3909
5336
  const p = makePrinter();
3910
5337
  const attestedBySequence = /* @__PURE__ */ new Map();
@@ -3930,6 +5357,8 @@ function renderBuildRecordV2Markdown(record2) {
3930
5357
  ...renderChecks(record2.observed.checks, p),
3931
5358
  ...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
5359
  ];
5360
+ if (record2.format === "halfcycle-build-record/v3")
5361
+ body.push(...renderReview(record2, p));
3933
5362
  if (total > 1) {
3934
5363
  body.push("## Earlier closes");
3935
5364
  for (const close of closes.slice(0, -1)) {
@@ -3944,7 +5373,7 @@ function renderBuildRecordV2Markdown(record2) {
3944
5373
  }
3945
5374
 
3946
5375
  // 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";
5376
+ import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync3, writeSync } from "node:fs";
3948
5377
  import { basename, join as join8, resolve, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
3949
5378
  var BUILD_RECORD_DIR = join8("docs", "build-records");
3950
5379
  function assertInsideOutDir(outDir, candidate, identity) {
@@ -3970,7 +5399,7 @@ function createExclusiveOnDisk(path, data) {
3970
5399
  closeSync(fd);
3971
5400
  }
3972
5401
  function renameOnDisk(from, to) {
3973
- renameSync(from, to);
5402
+ renameSync2(from, to);
3974
5403
  }
3975
5404
  var NODE_RECORD_PAIR_IO = {
3976
5405
  createExclusive: createExclusiveOnDisk,
@@ -3996,7 +5425,7 @@ function isOwnEarlierRecord(existingText, served) {
3996
5425
  } catch {
3997
5426
  return false;
3998
5427
  }
3999
- const existing = buildRecordV2Schema.safeParse(parsedJson);
5428
+ const existing = buildRecordSchema.safeParse(parsedJson);
4000
5429
  if (!existing.success)
4001
5430
  return false;
4002
5431
  const old = existing.data;
@@ -4079,6 +5508,23 @@ function replaceOne(io, tmp, finalPath, text) {
4079
5508
  // dist/build-record/obtain.js
4080
5509
  import { relative as relative3 } from "node:path";
4081
5510
 
5511
+ // dist/open-phase.js
5512
+ function writePhaseStamp(engagementId, phase, home) {
5513
+ if (!isEngagementId(engagementId)) {
5514
+ return { path: "(none \u2014 no valid project id)", reason: new InvalidEngagementIdError(engagementId).message };
5515
+ }
5516
+ const path = engagementEnvPath(engagementId, home);
5517
+ if (phase !== null && !isValidPhaseIdentity(phase)) {
5518
+ return { path, reason: new InvalidPhaseIdentityError(phase).message };
5519
+ }
5520
+ try {
5521
+ writeEngagementEnv(engagementId, { [PHASE_STAMP_ENV_KEY]: phase }, home, [PHASE_STAMP_ENV_KEY]);
5522
+ return null;
5523
+ } catch (err) {
5524
+ return { path, reason: err instanceof Error ? err.message : String(err) };
5525
+ }
5526
+ }
5527
+
4082
5528
  // dist/missing-route.js
4083
5529
  var MESSAGE = {
4084
5530
  GET: /^Route GET .* not found\.$/s,
@@ -4105,7 +5551,13 @@ var REFUSAL_REASONS = phaseRecordRefusalReasonSchema.options;
4105
5551
  function buildRecordUrl(credential, phase) {
4106
5552
  return `${credential.serviceUrl}/engagements/${encodeURIComponent(credential.engagementId)}/phases/${encodeURIComponent(phase)}/build-record`;
4107
5553
  }
4108
- async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_PAIR_IO) {
5554
+ function clearStampFor(engagementId, phase, home) {
5555
+ const stamped = readEngagementEnv(engagementId, home)?.[PHASE_STAMP_ENV_KEY];
5556
+ if (stamped !== phase)
5557
+ return null;
5558
+ return writePhaseStamp(engagementId, null, home);
5559
+ }
5560
+ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_PAIR_IO, home) {
4109
5561
  const url = buildRecordUrl(credential, phase);
4110
5562
  let res;
4111
5563
  let text;
@@ -4158,7 +5610,8 @@ async function obtainBuildRecord(credential, repoRoot, phase, io = NODE_RECORD_P
4158
5610
  kind: "written",
4159
5611
  jsonPath: relative3(repoRoot, result.jsonPath),
4160
5612
  mdPath: relative3(repoRoot, result.mdPath),
4161
- headline: latestCloseHeadline(answer.record)
5613
+ headline: latestCloseHeadline(answer.record),
5614
+ stampFailure: clearStampFor(credential.engagementId, phase, home)
4162
5615
  };
4163
5616
  }
4164
5617
  if (isMissingRoute("GET", res.status, raw)) {