@astrosheep/keiyaku 4.6.2 → 4.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +93 -45
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +3 -2
  3. package/build/src/akuma/providers/pi/events.js +3 -2
  4. package/build/src/body/render.d.ts +9 -1
  5. package/build/src/body/render.js +11 -0
  6. package/build/src/cli/commands/akuma-grammar.js +12 -3
  7. package/build/src/cli/commands/akuma.js +6 -0
  8. package/build/src/cli/commands/contract-grammar.js +7 -0
  9. package/build/src/cli/commands/contract-help.d.ts +1 -1
  10. package/build/src/cli/commands/contract-help.js +21 -9
  11. package/build/src/cli/commands/contract.js +3 -1
  12. package/build/src/cli/commands/task-grammar.js +9 -7
  13. package/build/src/cli/parse.js +5 -0
  14. package/build/src/cli/render/akuma-activity.d.ts +26 -6
  15. package/build/src/cli/render/akuma-activity.js +128 -118
  16. package/build/src/cli/render/akuma-tool.js +1 -1
  17. package/build/src/cli/render/audit.js +4 -6
  18. package/build/src/cli/render/catalog.js +7 -4
  19. package/build/src/cli/render/contract-history.js +8 -18
  20. package/build/src/cli/render/contract.js +5 -10
  21. package/build/src/cli/render/kanshi-akuma.js +1 -1
  22. package/build/src/cli/render/kanshi.js +24 -30
  23. package/build/src/cli/render/receipt.d.ts +1 -2
  24. package/build/src/cli/render/receipt.js +1 -6
  25. package/build/src/cli/render/refusal.js +2 -1
  26. package/build/src/cli/render/task.js +2 -1
  27. package/build/src/core/facts/codec.js +0 -6
  28. package/build/src/core/facts/fold.js +0 -7
  29. package/build/src/core/facts/types.d.ts +1 -4
  30. package/build/src/core/verbs/deliver.js +2 -14
  31. package/build/src/library/bind.d.ts +20 -20
  32. package/build/src/library/bind.js +2 -1
  33. package/build/src/library/contract-composition.d.ts +5 -2
  34. package/build/src/library/contract-composition.js +5 -3
  35. package/build/src/library/contract-creation.d.ts +7 -1
  36. package/build/src/library/contract-creation.js +19 -9
  37. package/build/src/library/contract-execution.d.ts +1 -0
  38. package/build/src/library/contract-execution.js +12 -4
  39. package/build/src/library/contract-settings.d.ts +2 -2
  40. package/build/src/library/contract-settings.js +21 -12
  41. package/build/src/library/outcome.d.ts +476 -652
  42. package/build/src/library/refusal.d.ts +2 -2
  43. package/build/src/protocol/bind.d.ts +1 -1
  44. package/build/src/protocol/deliver.js +1 -1
  45. package/build/src/protocol/execution-observation.d.ts +0 -8
  46. package/build/src/protocol/intent.js +2 -2
  47. package/build/src/protocol/operations.d.ts +1 -1
  48. package/build/src/protocol/read/status.d.ts +1 -1
  49. package/build/src/protocol/read/status.js +0 -2
  50. package/build/src/runtime/proc/windows-launch.exe +0 -0
  51. package/build/src/world.d.ts +1 -0
  52. package/build/src/world.js +14 -0
  53. package/package.json +3 -3
@@ -13,9 +13,8 @@ export function frameRule(headLines) {
13
13
  const width = headLines.reduce((widest, line) => Math.max(widest, displayColumns(line)), 0);
14
14
  return "─".repeat(width);
15
15
  }
16
- /** Tool rows one focused activity view keeps at its opening and final end; the surplus folds in place. */
17
- const OPENING_TOOL_BUDGET = 3;
18
- const RECENT_TOOL_BUDGET = 2;
16
+ /** Tool rows an activity view prints after each say or Tell checkpoint; the surplus folds in place. */
17
+ const TOOL_BUDGET = 3;
19
18
  function identity(id, alias) {
20
19
  return `${id}${alias === undefined ? "" : ` (${alias})`}`;
21
20
  }
@@ -434,57 +433,6 @@ function inFlightSay(activity, row) {
434
433
  }
435
434
  return newest !== undefined && newest.sequence === row.sequence;
436
435
  }
437
- function coalesceDeferredGaps(state) {
438
- state.deferred = state.deferred.reduce((entries, entry) => {
439
- const previous = entries.at(-1);
440
- if (entry.kind === "gap" && previous?.kind === "gap") {
441
- entries[entries.length - 1] = { kind: "gap", count: previous.count + entry.count };
442
- }
443
- else {
444
- entries.push(entry);
445
- }
446
- return entries;
447
- }, []);
448
- }
449
- function omitOldestDeferredTool(state) {
450
- const index = state.deferred.findIndex((entry) => entry.kind === "row" && isBoundedStreamTool(entry.row));
451
- if (index === -1)
452
- throw new Error("activity tail lost its pending tool");
453
- state.deferred[index] = { kind: "gap", count: 1 };
454
- coalesceDeferredGaps(state);
455
- }
456
- /** A say is a checkpoint: only tools after the last say may occupy the recent tail. */
457
- function omitPreSayTools(state) {
458
- state.deferred = state.deferred.map((entry) => entry.kind === "row" && isBoundedStreamTool(entry.row) ? { kind: "gap", count: 1 } : entry);
459
- coalesceDeferredGaps(state);
460
- }
461
- /** Emit only a prefix whose omission runs can no longer join an unresolved tail tool. */
462
- function flushSafeActivityPrefix(state, lines, context, layout) {
463
- for (;;) {
464
- const first = state.deferred[0];
465
- if (first === undefined || (first.kind === "row" && isBoundedStreamTool(first.row)))
466
- return;
467
- if (first.kind === "row") {
468
- state.deferred.shift();
469
- renderStreamRow(state, first.row, lines, { context, layout, inFlightSay: first.inFlightSay });
470
- continue;
471
- }
472
- const next = state.deferred[1];
473
- if (next === undefined || (next.kind === "row" && isBoundedStreamTool(next.row)))
474
- return;
475
- state.deferred.shift();
476
- lines.push(layout.marker(first.count));
477
- }
478
- }
479
- function renderCurrentTurnBoundary(state, activity, lines, context, layout) {
480
- const boundary = currentTurnBoundary(activity);
481
- if (boundary !== undefined && !state.renderedBoundaries.has(boundary.turnSequence)) {
482
- state.renderedBoundaries.add(boundary.turnSequence);
483
- if (boundary.row.kind !== "thought")
484
- renderStreamRow(state, boundary.row, lines, { context, layout });
485
- }
486
- return boundary;
487
- }
488
436
  function rememberLiveRows(state, activity) {
489
437
  for (const row of activity.rows) {
490
438
  if (row.kind === "tool" && row.state === "active")
@@ -503,8 +451,28 @@ function rememberLiveRows(state, activity) {
503
451
  state.liveRows.delete(row.sequence);
504
452
  }
505
453
  }
506
- function observeActivitySnapshot(state, activity, context, layout) {
507
- const lines = [];
454
+ /** A visible row first releases the untimed omission count accumulated before it. */
455
+ function emitRow(state, emissions, emission) {
456
+ if (state.omitted > 0)
457
+ emissions.push({ kind: "gap", count: state.omitted });
458
+ state.omitted = 0;
459
+ emissions.push(emission);
460
+ }
461
+ function emitCurrentTurnBoundary(state, activity, emissions) {
462
+ const boundary = currentTurnBoundary(activity);
463
+ if (boundary !== undefined && !state.renderedBoundaries.has(boundary.turnSequence)) {
464
+ state.renderedBoundaries.add(boundary.turnSequence);
465
+ if (boundary.row.kind !== "thought")
466
+ emitRow(state, emissions, { kind: "row", row: boundary.row });
467
+ }
468
+ }
469
+ /**
470
+ * Decide each newly settled row once, when first observed: it prints now or
471
+ * becomes part of an untimed omission count. No timestamped row is held for a
472
+ * later observation, so concurrent streams merge without moving backward.
473
+ */
474
+ function observeActivitySnapshot(state, activity) {
475
+ const emissions = [];
508
476
  const boundary = currentTurnBoundary(activity);
509
477
  rememberPendingTells(state, activity);
510
478
  // A row newly selected as this Turn's typed opening must still respect the
@@ -522,54 +490,48 @@ function observeActivitySnapshot(state, activity, context, layout) {
522
490
  row.sequence > state.newestSettledSequence);
523
491
  rememberLiveRows(state, activity);
524
492
  if (observedRows.length === 0)
525
- return lines;
493
+ return emissions;
526
494
  for (const row of observedRows)
527
495
  state.pendingTellSequences.delete(row.sequence);
528
496
  state.newestSettledSequence = observedRows.reduce((newest, row) => Math.max(newest, row.sequence), state.newestSettledSequence ?? observedRows[0].sequence);
529
- const rows = observedRows.filter((row) => row.kind !== "thought");
530
- for (const row of rows) {
531
- if (row.kind === "said") {
532
- omitPreSayTools(state);
533
- lines.push(...flushActivityTail(state, context, layout));
534
- renderStreamRow(state, row, lines, { context, layout, inFlightSay: inFlightSay(activity, row) });
535
- continue;
536
- }
537
- if (isBoundedStreamTool(row) && state.openingTools < OPENING_TOOL_BUDGET) {
538
- state.openingTools += 1;
539
- renderStreamRow(state, row, lines, { context, layout, inFlightSay: inFlightSay(activity, row) });
497
+ for (const row of observedRows) {
498
+ if (row.kind === "thought")
540
499
  continue;
500
+ // A failed tool is evidence, not routine progress, so it always prints and spends no budget.
501
+ const failed = row.kind === "tool" && typeof row.state === "object" && row.state.status === "error";
502
+ if (isBoundedStreamTool(row) && !failed) {
503
+ if (state.checkpointTools >= TOOL_BUDGET) {
504
+ state.omitted += 1;
505
+ continue;
506
+ }
507
+ state.checkpointTools += 1;
541
508
  }
542
- if (!isBoundedStreamTool(row) && state.deferred.length === 0) {
543
- renderStreamRow(state, row, lines, { context, layout, inFlightSay: inFlightSay(activity, row) });
544
- continue;
545
- }
546
- state.deferred.push({ kind: "row", row, inFlightSay: inFlightSay(activity, row) });
547
- if (isBoundedStreamTool(row)) {
548
- const pendingTools = state.deferred.filter((entry) => entry.kind === "row" && isBoundedStreamTool(entry.row)).length;
549
- if (pendingTools > RECENT_TOOL_BUDGET)
550
- omitOldestDeferredTool(state);
551
- }
552
- flushSafeActivityPrefix(state, lines, context, layout);
509
+ else if (row.kind === "said" || row.kind === "tell")
510
+ state.checkpointTools = 0;
511
+ emitRow(state, emissions, { kind: "row", row, inFlightSay: inFlightSay(activity, row) });
553
512
  }
554
- return lines;
513
+ return emissions;
555
514
  }
556
- function flushActivityTail(state, context, layout) {
515
+ function renderEmissions(state, emissions, context, layout) {
557
516
  const lines = [];
558
- for (const entry of state.deferred) {
559
- if (entry.kind === "gap")
560
- lines.push(layout.marker(entry.count));
517
+ for (const emission of emissions) {
518
+ if (emission.kind === "gap")
519
+ lines.push(layout.marker(emission.count));
561
520
  else
562
- renderStreamRow(state, entry.row, lines, { context, layout, inFlightSay: entry.inFlightSay });
521
+ renderStreamRow(state, emission.row, lines, {
522
+ context,
523
+ layout,
524
+ ...(emission.inFlightSay === undefined ? {} : { inFlightSay: emission.inFlightSay }),
525
+ ...(emission.unsettled === undefined ? {} : { unsettled: emission.unsettled }),
526
+ });
563
527
  }
564
- state.deferred = [];
565
528
  return lines;
566
529
  }
567
530
  /**
568
- * Append-only live view over one command's successive settled snapshots. The
569
- * first three tools stream immediately; later tools wait in a two-row tail.
570
- * A say omits the earlier tail, so only the last two extra tools after the
571
- * final say can print at closing. Newer tools displace older tail candidates
572
- * in place. Other narrative rows wait behind undecided tail tools.
531
+ * Append-only live view over one command's successive settled snapshots.
532
+ * After each say or Tell, the first three tools print as they settle; later
533
+ * tools until the next checkpoint fold into one omission marker. Failed tools
534
+ * always print.
573
535
  */
574
536
  export function activityStream(context, layout = plainLayout()) {
575
537
  const state = {
@@ -579,12 +541,12 @@ export function activityStream(context, layout = plainLayout()) {
579
541
  previousClock: undefined,
580
542
  renderedBoundaries: new Set(),
581
543
  admittedTellSequences: new Set(),
582
- openingTools: 0,
583
- deferred: [],
544
+ checkpointTools: 0,
545
+ omitted: 0,
584
546
  liveRows: new Map(),
585
547
  };
586
548
  const seed = (activity, alreadyRenderedSequence) => {
587
- const lines = [];
549
+ const emissions = [];
588
550
  if (alreadyRenderedSequence !== undefined) {
589
551
  state.admissionSequence = alreadyRenderedSequence;
590
552
  state.admittedTellSequences.add(alreadyRenderedSequence);
@@ -595,46 +557,54 @@ export function activityStream(context, layout = plainLayout()) {
595
557
  if (alreadyRenderedSequence !== undefined && boundary.row.sequence > alreadyRenderedSequence) {
596
558
  const beforeBoundary = baselineOmissionCount(activity, alreadyRenderedSequence, boundary.row.sequence);
597
559
  if (beforeBoundary > 0)
598
- lines.push(layout.marker(beforeBoundary));
560
+ emissions.push({ kind: "gap", count: beforeBoundary });
599
561
  }
600
562
  if (alreadyRenderedSequence !== undefined && boundary.row.sequence <= alreadyRenderedSequence)
601
563
  state.renderedBoundaries.add(boundary.turnSequence);
602
564
  else
603
- renderCurrentTurnBoundary(state, activity, lines, context, layout);
565
+ emitCurrentTurnBoundary(state, activity, emissions);
604
566
  rememberLiveRows(state, activity);
605
567
  const omitted = baselineOmissionCount(activity, alreadyRenderedSequence === undefined
606
568
  ? boundary.row.sequence
607
569
  : Math.max(boundary.row.sequence, alreadyRenderedSequence));
608
570
  if (omitted > 0)
609
- lines.push(layout.marker(omitted));
571
+ emissions.push({ kind: "gap", count: omitted });
610
572
  }
611
573
  rememberPendingTells(state, activity);
612
574
  rememberLiveRows(state, activity);
613
575
  const rows = settledRows(activity);
614
576
  if (rows.length > 0)
615
577
  state.newestSettledSequence = rows.reduce((newest, row) => Math.max(newest, row.sequence), rows[0].sequence);
616
- return lines;
578
+ return emissions;
617
579
  };
618
- const observe = (activity) => observeActivitySnapshot(state, activity, context, layout);
619
- const frame = () => groupedEntries([...state.liveRows.values()]
620
- .sort((left, right) => left.sequence - right.sequence)
621
- .map((row) => ({ kind: "row", row })), context, layout, { live: true, inFlightSay: (row) => row.kind === "said" });
622
580
  const flush = () => {
623
- if ([...state.liveRows.values()].some((row) => row.kind === "said"))
624
- omitPreSayTools(state);
625
- const lines = [...flushActivityTail(state, context, layout)];
581
+ const emissions = [];
582
+ if (state.omitted > 0)
583
+ emissions.push({ kind: "gap", count: state.omitted });
584
+ state.omitted = 0;
626
585
  for (const row of [...state.liveRows.values()].sort((left, right) => left.sequence - right.sequence)) {
627
586
  const unresolved = row.kind === "tool" && row.state === "active" ? { ...row, state: "unsettled" } : row;
628
- renderStreamRow(state, unresolved, lines, {
629
- context,
630
- layout,
631
- unsettled: true,
632
- });
587
+ emissions.push({ kind: "row", row: unresolved, unsettled: true });
633
588
  }
634
589
  state.liveRows.clear();
635
- return lines;
590
+ return emissions;
636
591
  };
637
- return Object.assign(observe, { seed, frame, flush });
592
+ const render = (emissions) => renderEmissions(state, emissions, context, layout);
593
+ const frame = () => groupedEntries([...state.liveRows.values()]
594
+ .sort((left, right) => left.sequence - right.sequence)
595
+ .map((row) => ({ kind: "row", row })), context, layout, { live: true, inFlightSay: (row) => row.kind === "said" });
596
+ const observe = (activity) => render(observeActivitySnapshot(state, activity));
597
+ return Object.assign(observe, {
598
+ seed: (activity, alreadyRenderedSequence) => render(seed(activity, alreadyRenderedSequence)),
599
+ frame,
600
+ flush: () => render(flush()),
601
+ emissions: {
602
+ observe: (activity) => observeActivitySnapshot(state, activity),
603
+ seed,
604
+ flush,
605
+ },
606
+ render,
607
+ });
638
608
  }
639
609
  function conclusionMarkVerb(status, outcome) {
640
610
  if (outcome === "failed" || (status !== undefined && failedOutcomeDiagnostic(status) !== undefined))
@@ -773,10 +743,11 @@ function observeWaitRound(state, round, context, now) {
773
743
  lines.push(...snapshotHeading(sole.status.id, sole.alias, sole.contract));
774
744
  }
775
745
  }
746
+ const parts = [];
776
747
  for (const { status, rows } of round) {
777
748
  const known = state.streams.get(status.id);
778
749
  if (known !== undefined) {
779
- lines.push(...known({ snapshot: status.timeline, rows }));
750
+ parts.push({ stream: known, emissions: known.emissions.observe({ snapshot: status.timeline, rows }) });
780
751
  }
781
752
  else {
782
753
  // Only a plural wait attributes its rows; a single-target stream keeps the plain row grammar.
@@ -785,13 +756,51 @@ function observeWaitRound(state, round, context, now) {
785
756
  : plainLayout());
786
757
  state.streams.set(status.id, stream);
787
758
  // Seed marks skipped retained evidence without spending this command's live tool budget.
788
- lines.push(...stream.seed({ snapshot: status.timeline, rows }));
759
+ parts.push({ stream, emissions: stream.emissions.seed({ snapshot: status.timeline, rows }) });
789
760
  }
790
761
  if (!state.settledAt.has(status.id) && defaultWaitComplete(status))
791
762
  state.settledAt.set(status.id, settleMoment(status) ?? now());
792
763
  }
764
+ lines.push(...mergeStreamEmissions(parts));
793
765
  return lines;
794
766
  }
767
+ /**
768
+ * Merge one observation's decided output across sources by row time. Each
769
+ * source keeps its own order; an omission count travels with the row after it,
770
+ * or with the row before it when it trails. Ties keep selection order.
771
+ */
772
+ function mergeStreamEmissions(parts) {
773
+ const queues = parts.map(({ stream, emissions }) => {
774
+ const units = [];
775
+ let pending = [];
776
+ for (const emission of emissions) {
777
+ pending.push(emission);
778
+ if (emission.kind !== "row")
779
+ continue;
780
+ const at = Date.parse(emission.row.at);
781
+ units.push({ at: Number.isFinite(at) ? at : (units.at(-1)?.at ?? Number.NEGATIVE_INFINITY), emissions: pending });
782
+ pending = [];
783
+ }
784
+ if (pending.length > 0) {
785
+ const last = units.at(-1);
786
+ if (last === undefined)
787
+ units.push({ at: Number.NEGATIVE_INFINITY, emissions: pending });
788
+ else
789
+ last.emissions.push(...pending);
790
+ }
791
+ return { stream, units };
792
+ });
793
+ const lines = [];
794
+ for (;;) {
795
+ let next;
796
+ for (const queue of queues)
797
+ if (queue.units.length > 0 && (next === undefined || queue.units[0].at < next.units[0].at))
798
+ next = queue;
799
+ if (next === undefined)
800
+ return lines;
801
+ lines.push(...next.stream.render(next.units.shift().emissions));
802
+ }
803
+ }
795
804
  /**
796
805
  * A conclusion clause asserts real waiting: a target that settled at or after
797
806
  * this wait began keeps its duration, a target already settled names no
@@ -851,10 +860,11 @@ function orderedWaitIds(state, result) {
851
860
  }
852
861
  return ids;
853
862
  }
863
+ function flushWaitStreams(state) {
864
+ return mergeStreamEmissions([...state.streams.values()].map((stream) => ({ stream, emissions: stream.emissions.flush() })));
865
+ }
854
866
  function concludeWaitStream(state, result, startedAt, now) {
855
- const tail = [];
856
- for (const stream of state.streams.values())
857
- tail.push(...stream.flush());
867
+ const tail = flushWaitStreams(state);
858
868
  const end = now();
859
869
  for (const observation of result.observations)
860
870
  registerWaitSource(state, observation.status.id, undefined, observation.contract);
@@ -913,7 +923,7 @@ export function waitObservationStream(context, options = {}) {
913
923
  };
914
924
  const observe = (round) => observeWaitRound(state, round, context, now);
915
925
  const frame = () => [...state.streams.values()].flatMap((stream) => stream.frame());
916
- const flush = () => [...state.streams.values()].flatMap((stream) => stream.flush());
926
+ const flush = () => flushWaitStreams(state);
917
927
  const conclude = (result) => concludeWaitStream(state, result, startedAt, now);
918
928
  return { select, observe, frame, flush, conclude, streamed: () => state.observed };
919
929
  }
@@ -98,7 +98,7 @@ function readText(call) {
98
98
  : call.offset !== undefined
99
99
  ? ` · from L${call.offset}`
100
100
  : call.limit !== undefined
101
- ? ` · ${call.limit} lines`
101
+ ? ` · ${call.limit} ${call.limit === 1 ? "line" : "lines"}`
102
102
  : "";
103
103
  return { label: "read", text: `${path}${detail}`, pathPreview: { before: "", path, detail } };
104
104
  }
@@ -1,5 +1,5 @@
1
1
  import { effectCleanup, effectExecutionStops } from "./effects.js";
2
- import { executionCleanupLines, executionStopLines, receiptPayload, receiptRow, reuseLines, stopLines, titleLines, } from "./receipt.js";
2
+ import { executionCleanupLines, executionStopLines, receiptPayload, receiptRow, stopLines, titleLines, } from "./receipt.js";
3
3
  import { renderRefusalFacts } from "./refusal.js";
4
4
  import { abbreviateGitIds, displayGitId } from "./contract-observation.js";
5
5
  import { DEFAULT_CLI_COLUMNS, gitShortStat, renderTextBlock, safeText } from "./terminal.js";
@@ -16,11 +16,10 @@ function candidateLines(report, columns, addressed, abbreviations) {
16
16
  return lines;
17
17
  }
18
18
  const identity = candidate.identity;
19
- receiptRow(lines, " ", "candidate", [{ text: "ready" }], columns);
20
- lines.push(`${CHILD}candidate ${displayGitId(identity.tenderSnapshot, abbreviations)}`);
19
+ receiptRow(lines, " ", "candidate", [{ text: "ready" }, { text: displayGitId(identity.tenderSnapshot, abbreviations), opaque: true }], columns);
21
20
  lines.push(`${CHILD}integration result ${displayGitId(identity.integration.snapshot, abbreviations)}`);
22
21
  if (!/^0{40}$/u.test(identity.integration.changeId))
23
- lines.push(`${CHILD}content identity (not commit) ${displayGitId(identity.integration.changeId, abbreviations)}`);
22
+ lines.push(`${CHILD}content identity ${displayGitId(identity.integration.changeId, abbreviations)}`);
24
23
  lines.push(...workspaceEvidence(candidate.workspace));
25
24
  lines.push(...renderTextBlock(gitShortStat(candidate.scope), CHILD, columns));
26
25
  if (candidate.scope.paths !== undefined) {
@@ -47,7 +46,6 @@ function verificationLines(verification, columns, addressed) {
47
46
  }
48
47
  if (verification.kind === "reused") {
49
48
  receiptRow(lines, " ", "verification", [{ text: "reused" }, { text: verification.verdict }], columns);
50
- lines.push(...reuseLines(verification, columns));
51
49
  if (verification.summary !== undefined)
52
50
  receiptPayload(lines, "summary", auditSummary(verification.summary));
53
51
  return lines;
@@ -77,7 +75,7 @@ function targetLines(report, columns, addressed, abbreviations) {
77
75
  const target = report.target;
78
76
  const lines = [];
79
77
  if (target.kind === "not-observed") {
80
- receiptRow(lines, " ", "target", [{ text: "not-observed" }], columns);
78
+ receiptRow(lines, " ", "target", [{ text: "none" }], columns);
81
79
  return lines;
82
80
  }
83
81
  if (target.kind === "placeable") {
@@ -81,10 +81,13 @@ export function renderArchetypeCatalogue(list) {
81
81
  ? []
82
82
  : [
83
83
  "",
84
- ...list.rows.flatMap((row) => [
85
- `${safeText(row.name)}${row.model === undefined ? "" : ` ${safeText(row.model)}`}`,
86
- ...(row.description === undefined ? [] : [` ${safeText(row.description)}`]),
87
- ]),
84
+ ...list.rows.flatMap((row) => {
85
+ const facts = [
86
+ ...(row.model === undefined ? [] : [safeText(row.model)]),
87
+ ...(row.description === undefined ? [] : [safeText(row.description)]),
88
+ ];
89
+ return [safeText(row.name), ...(facts.length === 0 ? [] : [` ${facts.join(" — ")}`])];
90
+ }),
88
91
  ...(list.hasMore ? ["…"] : []),
89
92
  ]),
90
93
  ].join("\n");
@@ -47,15 +47,13 @@ function journalBody(fact, workspace) {
47
47
  }
48
48
  case "amend":
49
49
  return [...listFact("gates", fact.data.gates), ...listFact("after", fact.data.after)];
50
- case "bound":
51
- return [];
52
50
  case "deliver": {
53
51
  const { tenderSnapshot, integration, method, policy } = fact.data;
54
52
  return [
55
53
  ` candidate ${shortGitId(tenderSnapshot)}`,
56
54
  ` predecessor commit ${shortGitId(integration.predecessor)}`,
57
55
  ` integration result ${shortGitId(integration.snapshot)}`,
58
- ` content identity (not commit) ${integration.changeId}`,
56
+ ` content identity ${integration.changeId}`,
59
57
  ` method ${method}`,
60
58
  ` require branches up to date ${String(policy.requireBranchesToBeUpToDate)}`,
61
59
  ];
@@ -160,6 +158,7 @@ function latestVerification(beat) {
160
158
  }
161
159
  return latest;
162
160
  }
161
+ const lineCount = (count) => `${count} line${count === 1 ? "" : "s"}`;
163
162
  function skeletonBeatLines(beat, history) {
164
163
  const first = beat.events[0];
165
164
  if (first === undefined)
@@ -167,9 +166,9 @@ function skeletonBeatLines(beat, history) {
167
166
  const full = beat.events.flatMap((event) => fullEventLines(event, history.workspace));
168
167
  const evidenceSuffix = () => {
169
168
  const count = foldedPayloadLines(full);
170
- return count > 0 ? ` · ${count} lines` : "";
169
+ return count > 0 ? ` · ${lineCount(count)}` : "";
171
170
  };
172
- const payloadSuffix = (rendered) => rendered.length > 0 ? ` · ${rendered.length} lines` : "";
171
+ const payloadSuffix = (rendered) => rendered.length > 0 ? ` · ${lineCount(rendered.length)}` : "";
173
172
  if (first.source === "dispatch")
174
173
  return [`${clock(first)} dispatch · ${first.dispatch.akuId}`];
175
174
  const fact = first.fact;
@@ -178,8 +177,6 @@ function skeletonBeatLines(beat, history) {
178
177
  const dispatch = beat.events.find((event) => event.source === "dispatch");
179
178
  return [`${clock(first)} ${bindLine(fact, dispatch)}`];
180
179
  }
181
- case "bound":
182
- return [`${clock(first)} bound`];
183
180
  case "deliver": {
184
181
  const verdict = latestVerification(beat)?.data.verdict;
185
182
  return [
@@ -213,7 +210,7 @@ function skeletonBeatLines(beat, history) {
213
210
  ];
214
211
  default: {
215
212
  const folded = foldedPayloadLines(full);
216
- return [`${clock(first)} ${eventKind(first)}${folded > 0 ? ` · ${folded} lines` : ""}`];
213
+ return [`${clock(first)} ${eventKind(first)}${folded > 0 ? ` · ${lineCount(folded)}` : ""}`];
217
214
  }
218
215
  }
219
216
  }
@@ -227,8 +224,6 @@ function skeletonState(history) {
227
224
  return lifecycleWord("claimed");
228
225
  if (journal.some((event) => event.fact.kind === "abandoned"))
229
226
  return "abandoned";
230
- if (journal.some((event) => event.fact.kind === "deliver" || event.fact.kind === "reintegrated"))
231
- return "delivered";
232
227
  return "bound";
233
228
  }
234
229
  function skeletonBeats(history) {
@@ -244,15 +239,10 @@ function skeletonBeats(history) {
244
239
  if (event.source === "journal" && event.fact.kind === "bind") {
245
240
  for (let next = index + 1; next < events.length; next += 1) {
246
241
  const candidate = events[next];
247
- if (candidate.source === "journal" && candidate.fact.kind === "bound") {
248
- grouped.push(candidate);
249
- consumed.add(next);
242
+ if (candidate.source !== "dispatch")
250
243
  break;
251
- }
252
- if (candidate.source === "dispatch") {
253
- grouped.push(candidate);
254
- consumed.add(next);
255
- }
244
+ grouped.push(candidate);
245
+ consumed.add(next);
256
246
  }
257
247
  }
258
248
  if (event.source === "journal" && (event.fact.kind === "deliver" || event.fact.kind === "reintegrated")) {
@@ -1,6 +1,6 @@
1
1
  import { effectCleanup, effectExecutionStops, effectLags, effectRecoverySnapshot, effectRetainedCheckouts, effectRetainedWorktrees, effectRetiredWorktrees, effectSettlementLags, } from "./effects.js";
2
2
  import { renderAcceptedAudit } from "./audit.js";
3
- import { appendHookPayload, executionCleanupLines, executionStopLines, gatesAwaitLines, hookFailureSummary, outcomeLines, receiptPayload, receiptRow, reuseLines, stopLines, titleLines, } from "./receipt.js";
3
+ import { appendHookPayload, executionCleanupLines, executionStopLines, gatesAwaitLines, hookFailureSummary, outcomeLines, receiptPayload, receiptRow, stopLines, titleLines, } from "./receipt.js";
4
4
  import { abbreviateGitIds, displayGitId, lifecycleWord } from "./contract-observation.js";
5
5
  import { renderDiffstat } from "./akuma-tool.js";
6
6
  import { DEFAULT_CLI_COLUMNS, renderOpaqueBlock, safeText, tone } from "./terminal.js";
@@ -163,9 +163,6 @@ function acceptedRecord(result, columns) {
163
163
  const recoverySnapshot = effectRecoverySnapshot(result.effects);
164
164
  if (recoverySnapshot !== undefined)
165
165
  receiptRow(record, " ", "recovery snapshot", [{ text: recoverySnapshot, opaque: true }], columns);
166
- if (result.operation === "deliver") {
167
- pushBlock(record, reuseLines(result.value.verificationReuse, columns));
168
- }
169
166
  return record;
170
167
  }
171
168
  function acceptedLagRows(result, columns) {
@@ -367,14 +364,14 @@ function renderAcceptedAmend(result, columns, color) {
367
364
  lines.push(...acceptedDeviations(result, columns, color), ...recordBlock(result, columns));
368
365
  return lines.join("\n");
369
366
  }
370
- function deliverIdentityLines(result, abbreviations, columns) {
367
+ function deliverIdentityLines(result, abbreviations, columns, kept) {
371
368
  const lines = [];
372
369
  if (result.value.leading !== undefined && result.value.leading.kind === "already-admitted")
373
370
  receiptRow(lines, " ", "leading", [{ text: result.value.leading.kind.replaceAll("-", " ") }], columns);
374
371
  if (result.value.tenderSnapshot !== undefined)
375
- receiptRow(lines, " ", "candidate", [{ text: displayGitId(result.value.tenderSnapshot, abbreviations), opaque: true }], columns);
372
+ receiptRow(lines, " ", "candidate", [{ text: `${displayGitId(result.value.tenderSnapshot, abbreviations)}${kept ? " · kept" : ""}`, opaque: true }], columns);
376
373
  if (result.value.integration !== undefined && !/^0{40}$/u.test(result.value.integration.changeId))
377
- receiptRow(lines, " ", "content identity (not commit)", [{ text: displayGitId(result.value.integration.changeId, abbreviations), opaque: true }], columns);
374
+ receiptRow(lines, " ", "content identity", [{ text: displayGitId(result.value.integration.changeId, abbreviations), opaque: true }], columns);
378
375
  return lines;
379
376
  }
380
377
  function renderAcceptedDeliver(result, columns) {
@@ -387,7 +384,7 @@ function renderAcceptedDeliver(result, columns) {
387
384
  result.value.completion?.predecessor ?? "",
388
385
  result.value.completion?.integration ?? "",
389
386
  ]);
390
- lines.push(...deliverIdentityLines(result, abbreviations, columns));
387
+ lines.push(...deliverIdentityLines(result, abbreviations, columns, !complete));
391
388
  if (complete)
392
389
  lines.push(...completedPlacementLines(result, columns));
393
390
  else {
@@ -404,8 +401,6 @@ function renderAcceptedDeliver(result, columns) {
404
401
  lines.push(...stopLines(result.value.verification, columns, result.contract));
405
402
  if (!complete && result.value.placement !== undefined)
406
403
  lines.push(...stopLines(result.value.placement, columns, result.contract));
407
- if (!complete)
408
- receiptRow(lines, " ", "candidate", [{ text: "kept" }], columns);
409
404
  lines.push(...continuationLines(result, columns), ...(complete ? obligationLines(result, columns) : recordBlock(result, columns)), ...worktreeRetirementLines(result, columns), ...gatesAwaitLines(result.value.placement));
410
405
  return lines.join("\n");
411
406
  }
@@ -41,7 +41,7 @@ function endpointFact(id, observed) {
41
41
  function renderAkuma(report, context) {
42
42
  const section = report.akuma;
43
43
  if (section.kind === "absent")
44
- return ["AKUMA // absent", "", " akuma absent"];
44
+ return ["AKUMA // absent"];
45
45
  if (section.kind === "failed")
46
46
  return ["AKUMA // unavailable", "", tone(`! ${safeText(section.failure.message)}`, "alert", context.color)];
47
47
  const rows = section.value.rows;