vericify 1.0.1 → 1.1.0

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.
@@ -7,6 +7,7 @@ import {
7
7
  checkpointTriggerFromStatusEvent,
8
8
  checkpointTriggerFromWorkspaceSummary,
9
9
  } from "../checkpoints/policy.js";
10
+ import { collapseAndScoreActivityItems, compareRunsByAttention, deriveRunLiveSignal } from "./live-signal.js";
10
11
 
11
12
  function sortByUpdatedDescending(items) {
12
13
  return [...items].sort((left, right) => String(right.updated_at).localeCompare(String(left.updated_at)));
@@ -61,6 +62,85 @@ function statusFromProcessPostKind(kind) {
61
62
  return "running";
62
63
  }
63
64
 
65
+ const OBSERVER_STATUS_EVENT_TYPES = new Set([
66
+ "HUB_RUN_FOCUSED",
67
+ "HUB_VIEW_CHANGED",
68
+ "HUB_REFRESHED",
69
+ "HUB_COMMAND_WRITTEN",
70
+ "HUB_SESSION_STARTED",
71
+ "HUB_SESSION_STOPPED",
72
+ "HUB_SESSION_RESUMED",
73
+ ]);
74
+
75
+ const OBSERVER_LEDGER_CATEGORIES = new Set([
76
+ "operator_focus",
77
+ "operator_view",
78
+ "operator_refresh",
79
+ "operator_write",
80
+ ]);
81
+
82
+ const PRIMARY_PROCESS_POST_KINDS = new Set([
83
+ "blocker",
84
+ "completion",
85
+ "handoff_note",
86
+ "operator_note",
87
+ "intervention",
88
+ "stale_ack",
89
+ ]);
90
+
91
+ function isObserverStatusEvent(event) {
92
+ return event?.payload?.observer_telemetry === true ||
93
+ (event?.source_module === "vericify-hub" && OBSERVER_STATUS_EVENT_TYPES.has(event?.event_type));
94
+ }
95
+
96
+ function isObserverLedgerEntry(entry) {
97
+ return entry?.metadata?.observer_telemetry === true ||
98
+ (entry?.tool === "vericify-hub" && OBSERVER_LEDGER_CATEGORIES.has(entry?.category));
99
+ }
100
+
101
+ function recordSourceMetadata(record) {
102
+ return {
103
+ record_source: record.record_source,
104
+ record_source_kind: record.record_source_kind,
105
+ record_source_path: record.record_source_path,
106
+ record_sources: Array.isArray(record.record_sources) ? record.record_sources : [],
107
+ };
108
+ }
109
+
110
+ function signalPolicyForProcessPost(post) {
111
+ return {
112
+ signal_class: PRIMARY_PROCESS_POST_KINDS.has(post.kind) ? "primary" : "secondary",
113
+ visible_by_default: true,
114
+ };
115
+ }
116
+
117
+ function signalPolicyForStatusEvent(event) {
118
+ if (isObserverStatusEvent(event)) {
119
+ return {
120
+ signal_class: "background",
121
+ visible_by_default: false,
122
+ };
123
+ }
124
+ const normalizedStatus = statusFromEventStatus(event.status);
125
+ return {
126
+ signal_class: normalizedStatus === "blocked" || normalizedStatus === "completed" ? "primary" : "secondary",
127
+ visible_by_default: true,
128
+ };
129
+ }
130
+
131
+ function signalPolicyForLedgerEntry(entry) {
132
+ if (isObserverLedgerEntry(entry)) {
133
+ return {
134
+ signal_class: "background",
135
+ visible_by_default: false,
136
+ };
137
+ }
138
+ return {
139
+ signal_class: entry.category === "regression" ? "primary" : "secondary",
140
+ visible_by_default: true,
141
+ };
142
+ }
143
+
64
144
  function humanizeToken(value) {
65
145
  return String(value ?? "")
66
146
  .replace(/[-_]+/g, " ")
@@ -80,6 +160,8 @@ function activityItemFromProcessPost(post) {
80
160
  status: statusFromProcessPostKind(post.kind),
81
161
  summary: post.summary,
82
162
  refs: unique([...asArray(post.evidence_refs), ...asArray(post.tool_refs)]),
163
+ ...recordSourceMetadata(post),
164
+ ...signalPolicyForProcessPost(post),
83
165
  };
84
166
  }
85
167
 
@@ -96,6 +178,8 @@ function activityItemFromStatusEvent(runId, event) {
96
178
  status: statusFromEventStatus(event.status),
97
179
  summary: event.payload?.summary ?? event.event_type,
98
180
  refs: asArray(event.payload?.evidence_ref),
181
+ ...recordSourceMetadata(event),
182
+ ...signalPolicyForStatusEvent(event),
99
183
  };
100
184
  }
101
185
 
@@ -112,6 +196,8 @@ function activityItemFromLedgerEntry(runId, entry) {
112
196
  status: statusFromLedgerCategory(entry.category),
113
197
  summary: entry.message,
114
198
  refs: asArray(entry.artifacts),
199
+ ...recordSourceMetadata(entry),
200
+ ...signalPolicyForLedgerEntry(entry),
115
201
  };
116
202
  }
117
203
 
@@ -140,6 +226,12 @@ function activityItemFromLaneHeartbeat(runDetail, lane, latestActivity) {
140
226
  status: lane.status,
141
227
  summary: summarizeLaneHeartbeat(lane, latestActivity),
142
228
  refs: latestActivity?.refs ?? [],
229
+ record_source: "derived",
230
+ record_source_kind: "lane_heartbeat",
231
+ record_source_path: undefined,
232
+ record_sources: [],
233
+ signal_class: "continuity",
234
+ visible_by_default: false,
143
235
  };
144
236
  }
145
237
 
@@ -227,7 +319,7 @@ function deltaBetween(left, right, id) {
227
319
  };
228
320
  }
229
321
 
230
- function summarizeRun(run, branches, lanes, nodes, handoffs, checkpoints, neighbors) {
322
+ function summarizeRun(run, branches, lanes, nodes, handoffs, checkpoints, neighbors, liveSignal) {
231
323
  const latestCheckpoint = checkpoints[checkpoints.length - 1];
232
324
  return {
233
325
  run_id: run.run_id,
@@ -241,6 +333,19 @@ function summarizeRun(run, branches, lanes, nodes, handoffs, checkpoints, neighb
241
333
  updated_at: run.updated_at,
242
334
  similar_runs: neighbors,
243
335
  latest_checkpoint: latestCheckpoint,
336
+ attention_class: liveSignal?.attention_class,
337
+ attention_reason: liveSignal?.attention_reason,
338
+ attention_score: liveSignal?.attention_score,
339
+ attention_rank: liveSignal?.attention_rank,
340
+ freshness_class: liveSignal?.freshness_class,
341
+ expected_next_event: liveSignal?.expected_next_event,
342
+ overdue_at: liveSignal?.overdue_at,
343
+ current_actor_id: liveSignal?.current_actor_id,
344
+ waiting_on_actor_id: liveSignal?.waiting_on_actor_id,
345
+ needs_narrative: liveSignal?.needs_narrative,
346
+ recommended_post_kind: liveSignal?.recommended_post_kind,
347
+ signal_confidence: liveSignal?.signal_confidence,
348
+ partner_dependency_class: liveSignal?.partner_dependency_class,
244
349
  };
245
350
  }
246
351
 
@@ -326,16 +431,18 @@ function buildHandoffRun(handoff, workspace) {
326
431
  }
327
432
 
328
433
  function buildWorkspaceRun(workspaceState, workspace) {
434
+ const meaningfulEvents = workspaceState.statusEvents.filter((event) => !isObserverStatusEvent(event));
435
+ const meaningfulLedger = workspaceState.ledgerEntries.filter((entry) => !isObserverLedgerEntry(entry));
329
436
  if (
330
437
  workspaceState.todoNodes.length === 0 &&
331
- workspaceState.statusEvents.length === 0 &&
332
- workspaceState.ledgerEntries.length === 0 &&
438
+ meaningfulEvents.length === 0 &&
439
+ meaningfulLedger.length === 0 &&
333
440
  workspaceState.processPosts.length === 0
334
441
  ) return null;
335
442
  const runId = "workspace:current";
336
443
  const branchId = `${runId}:main`;
337
- const activeEvents = workspaceState.statusEvents.slice(-25);
338
- const activeLedger = workspaceState.ledgerEntries.slice(-12);
444
+ const activeEvents = meaningfulEvents.slice(-25);
445
+ const activeLedger = meaningfulLedger.slice(-12);
339
446
  const activePosts = workspaceState.processPosts.slice(-12);
340
447
  const laneMap = new Map();
341
448
  for (const event of activeEvents) {
@@ -402,11 +509,16 @@ function buildWorkspaceRun(workspaceState, workspace) {
402
509
  title: "Workspace Current",
403
510
  objective: "Current workspace execution state",
404
511
  status: branch.status,
405
- created_at: workspaceState.statusEvents[0]?.timestamp ?? isoNow(),
512
+ created_at: firstDefined(
513
+ meaningfulEvents[0]?.timestamp,
514
+ activePosts[0]?.timestamp,
515
+ meaningfulLedger[0]?.timestamp_utc,
516
+ isoNow()
517
+ ),
406
518
  updated_at: firstDefined(
407
519
  workspaceState.processPosts[workspaceState.processPosts.length - 1]?.timestamp,
408
- workspaceState.statusEvents[workspaceState.statusEvents.length - 1]?.timestamp,
409
- workspaceState.ledgerEntries[workspaceState.ledgerEntries.length - 1]?.timestamp_utc,
520
+ meaningfulEvents[meaningfulEvents.length - 1]?.timestamp,
521
+ meaningfulLedger[meaningfulLedger.length - 1]?.timestamp_utc,
410
522
  isoNow()
411
523
  ),
412
524
  branch_ids: [branchId],
@@ -441,18 +553,22 @@ function buildObjectiveRuns(workspaceState, workspace) {
441
553
  grouped.get(key).push(event);
442
554
  }
443
555
  return [...grouped.entries()].map(([runId, events]) => {
556
+ if (runId === "workspace:current") return null;
444
557
  const sortedEvents = sortByTimestamp(events, (event) => event.timestamp ?? "");
445
- const branchIds = unique(sortedEvents.map((event) => explicitBranchId(runId, event)));
446
- const laneIds = unique(sortedEvents.map((event) => explicitLaneId(runId, event)));
558
+ const meaningfulEvents = sortedEvents.filter((event) => !isObserverStatusEvent(event));
559
+ if (!meaningfulEvents.length) return null;
560
+ const latestMeaningfulEvent = meaningfulEvents[meaningfulEvents.length - 1];
561
+ const branchIds = unique(meaningfulEvents.map((event) => explicitBranchId(runId, event)));
562
+ const laneIds = unique(meaningfulEvents.map((event) => explicitLaneId(runId, event)));
447
563
  const branches = branchIds.map((branchId) => ({
448
564
  branch_id: branchId,
449
565
  run_id: runId,
450
566
  name: branchId === `${runId}:main` ? "main" : branchId.split(":").at(-1) ?? "branch",
451
- status: statusFromEventStatus(sortedEvents[sortedEvents.length - 1]?.status),
567
+ status: statusFromEventStatus(latestMeaningfulEvent?.status),
452
568
  lane_ids: laneIds.filter((laneId) => laneId.startsWith(branchId) || branchId === `${runId}:main`),
453
569
  }));
454
570
  const lanes = laneIds.map((laneId) => {
455
- const related = sortedEvents.filter((event) => explicitLaneId(runId, event) === laneId);
571
+ const related = meaningfulEvents.filter((event) => explicitLaneId(runId, event) === laneId);
456
572
  const latest = related[related.length - 1];
457
573
  return {
458
574
  lane_id: laneId,
@@ -463,7 +579,7 @@ function buildObjectiveRuns(workspaceState, workspace) {
463
579
  updated_at: latest.timestamp ?? isoNow(),
464
580
  };
465
581
  });
466
- const checkpoints = sortedEvents.map((event, index) => checkpointFromSummary({
582
+ const checkpoints = meaningfulEvents.map((event, index) => checkpointFromSummary({
467
583
  checkpointId: `${runId}:cp:${index + 1}`,
468
584
  runId,
469
585
  branchId: explicitBranchId(runId, event),
@@ -473,7 +589,7 @@ function buildObjectiveRuns(workspaceState, workspace) {
473
589
  taskDelta: event.payload?.summary ?? event.event_type,
474
590
  evidenceRefs: event.payload?.evidence_ref ? [event.payload.evidence_ref] : [],
475
591
  gitCommitSha: firstDefined(event.git_commit_sha, event.payload?.git_commit_sha, event.metadata?.git_commit_sha),
476
- checkpointMeta: checkpointTriggerFromStatusEvent(event, sortedEvents[index - 1]),
592
+ checkpointMeta: checkpointTriggerFromStatusEvent(event, meaningfulEvents[index - 1]),
477
593
  }));
478
594
  const deltas = checkpoints.length > 1
479
595
  ? checkpoints.slice(1).map((checkpoint, index) => deltaBetween(checkpoints[index], checkpoint, `${runId}:delta:${index + 1}`))
@@ -481,11 +597,11 @@ function buildObjectiveRuns(workspaceState, workspace) {
481
597
  const run = {
482
598
  run_id: runId,
483
599
  workspace,
484
- title: sortedEvents[sortedEvents.length - 1]?.payload?.summary ?? runId,
485
- objective: sortedEvents[0]?.objective_id ?? sortedEvents[0]?.trace_id ?? runId,
486
- status: statusFromEventStatus(sortedEvents[sortedEvents.length - 1]?.status),
487
- created_at: sortedEvents[0]?.timestamp ?? isoNow(),
488
- updated_at: sortedEvents[sortedEvents.length - 1]?.timestamp ?? isoNow(),
600
+ title: latestMeaningfulEvent?.payload?.summary ?? runId,
601
+ objective: meaningfulEvents[0]?.objective_id ?? meaningfulEvents[0]?.trace_id ?? runId,
602
+ status: statusFromEventStatus(latestMeaningfulEvent?.status),
603
+ created_at: meaningfulEvents[0]?.timestamp ?? isoNow(),
604
+ updated_at: latestMeaningfulEvent?.timestamp ?? isoNow(),
489
605
  branch_ids: branches.map((branch) => branch.branch_id),
490
606
  lane_ids: lanes.map((lane) => lane.lane_id),
491
607
  checkpoint_ids: checkpoints.map((checkpoint) => checkpoint.checkpoint_id),
@@ -502,7 +618,7 @@ function buildObjectiveRuns(workspaceState, workspace) {
502
618
  activity_items: [],
503
619
  similarity: { target_run_id: runId, neighbors: [] },
504
620
  };
505
- });
621
+ }).filter(Boolean);
506
622
  }
507
623
 
508
624
  function ensureBranch(runDetail, branchId) {
@@ -678,7 +794,9 @@ function attachActivityItems(runs, workspaceState) {
678
794
 
679
795
  function attachLaneHeartbeats(runs) {
680
796
  for (const runDetail of runs) {
681
- const sourceItems = (runDetail.activity_items ?? []).filter((item) => item.source_kind !== "lane_heartbeat");
797
+ const sourceItems = (runDetail.activity_items ?? []).filter((item) =>
798
+ item.source_kind !== "lane_heartbeat" && item.visible_by_default !== false
799
+ );
682
800
  for (const lane of runDetail.lanes ?? []) {
683
801
  const latestActivity = sortByTimestamp(
684
802
  sourceItems.filter((item) => item.lane_id === lane.lane_id || (!item.lane_id && item.actor_id === lane.agent_id)),
@@ -689,9 +807,16 @@ function attachLaneHeartbeats(runs) {
689
807
  }
690
808
  }
691
809
 
810
+ function collapseActivityItems(runs, now) {
811
+ for (const runDetail of runs) {
812
+ runDetail.activity_items = collapseAndScoreActivityItems(runDetail.activity_items ?? [], { now });
813
+ }
814
+ }
815
+
692
816
  function attachLedgerEntries(runs, workspaceState) {
693
817
  const byId = new Map(runs.map((run) => [run.run.run_id, run]));
694
818
  for (const entry of sortByTimestamp(workspaceState.ledgerEntries, (item) => item.timestamp_utc ?? "")) {
819
+ if (isObserverLedgerEntry(entry)) continue;
695
820
  const runId = runIdFromLedgerEntry(entry);
696
821
  if (!runId) continue;
697
822
  const runDetail = byId.get(runId);
@@ -769,6 +894,16 @@ function attachSimilarity(runs) {
769
894
  target_run_id: run.run.run_id,
770
895
  neighbors,
771
896
  };
897
+ }
898
+ }
899
+
900
+ function attachLiveSignals(runs, now) {
901
+ for (const run of runs) {
902
+ run.live_signal = deriveRunLiveSignal(run, { now });
903
+ }
904
+ const ranked = [...runs].sort(compareRunsByAttention);
905
+ ranked.forEach((run, index) => {
906
+ run.live_signal.attention_rank = index + 1;
772
907
  run.run_summary = summarizeRun(
773
908
  run.run,
774
909
  run.branches,
@@ -776,12 +911,15 @@ function attachSimilarity(runs) {
776
911
  run.nodes,
777
912
  run.handoff_timeline.items,
778
913
  run.recent_checkpoints,
779
- neighbors
914
+ run.similarity?.neighbors ?? [],
915
+ run.live_signal
780
916
  );
781
- }
917
+ });
918
+ return ranked;
782
919
  }
783
920
 
784
- export function projectWorkspaceState(workspaceState) {
921
+ export function projectWorkspaceState(workspaceState, options = {}) {
922
+ const now = options.now ?? isoNow();
785
923
  const workspace = workspaceRef(workspaceState.workspaceRoot);
786
924
  const runs = [
787
925
  ...workspaceState.handoffs.map((handoff) => buildHandoffRun(handoff, workspace)),
@@ -791,19 +929,36 @@ export function projectWorkspaceState(workspaceState) {
791
929
  if (workspaceRun) runs.push(workspaceRun);
792
930
  attachProcessPosts(runs, workspaceState, workspace);
793
931
  attachActivityItems(runs, workspaceState);
794
- attachLaneHeartbeats(runs);
795
932
  attachLedgerEntries(runs, workspaceState);
933
+ collapseActivityItems(runs, now);
934
+ attachLaneHeartbeats(runs);
796
935
  finalizeRunDetails(runs);
797
936
  attachSimilarity(runs);
798
- const sorted = sortByUpdatedDescending(runs.map((run) => ({
799
- ...run,
800
- updated_at: run.run.updated_at,
801
- }))).map(({ updated_at, ...rest }) => rest);
937
+ const sorted = attachLiveSignals(runs, now);
802
938
  return {
803
- generated_at: isoNow(),
939
+ generated_at: now,
804
940
  workspace,
805
941
  adapter_profiles: workspaceState.adapterProfiles ?? [],
806
942
  run_summaries: sorted.map((run) => run.run_summary),
807
943
  runs: sorted,
944
+ attention_items: sorted.map((run) => ({
945
+ run_id: run.run.run_id,
946
+ title: run.run.title,
947
+ status: run.run.status,
948
+ attention_rank: run.live_signal?.attention_rank,
949
+ attention_class: run.live_signal?.attention_class,
950
+ attention_reason: run.live_signal?.attention_reason,
951
+ attention_score: run.live_signal?.attention_score,
952
+ why_now: run.live_signal?.why_now,
953
+ expected_next_event: run.live_signal?.expected_next_event,
954
+ overdue_at: run.live_signal?.overdue_at,
955
+ recommended_owner_action: run.live_signal?.recommended_owner_action,
956
+ recommended_post_kind: run.live_signal?.recommended_post_kind,
957
+ needs_narrative: run.live_signal?.needs_narrative,
958
+ freshness_class: run.live_signal?.freshness_class,
959
+ signal_confidence: run.live_signal?.signal_confidence,
960
+ current_actor_id: run.live_signal?.current_actor_id,
961
+ waiting_on_actor_id: run.live_signal?.waiting_on_actor_id,
962
+ })),
808
963
  };
809
964
  }
package/src/tui/app.js CHANGED
@@ -11,6 +11,7 @@ import { upsertTodoNode } from "../store/todo-state.js";
11
11
  import { formatTimestamp } from "../core/util.js";
12
12
  import { completeCommand, executeCommand } from "./commands.js";
13
13
  import {
14
+ buildAdaptersSections,
14
15
  buildCompareSections,
15
16
  buildHistorySections,
16
17
  buildHubMetrics,
@@ -165,7 +166,9 @@ export async function runHub({ workspaceRoot, feedFile, loadState, watchedPaths
165
166
  ? buildInspectSections(selected, { tier })
166
167
  : state.view === "history"
167
168
  ? buildHistorySections(state.projected, selected, { tier })
168
- : buildHubSections(state.projected, selected, { tier })),
169
+ : state.view === "adapters"
170
+ ? buildAdaptersSections(state.projected, { tier })
171
+ : buildHubSections(state.projected, selected, { tier })),
169
172
  ];
170
173
 
171
174
  line(boxTitle(` Vericify Hub | ${state.mode} | ${workspaceRoot} `, width));
@@ -175,7 +178,7 @@ export async function runHub({ workspaceRoot, feedFile, loadState, watchedPaths
175
178
  renderPanelGrid(sections, width, tier);
176
179
  const prompt = state.mode === "command"
177
180
  ? `/${state.commandBuffer}`
178
- : "[q quit] [j/k move] [Enter inspect] [c compare] [y history] [r refresh] [/ command] [h hub]";
181
+ : "[q quit] [j/k move] [Enter inspect] [c compare] [y history] [a adapters] [r refresh] [/ command] [h hub]";
179
182
  line(truncate(prompt, width));
180
183
  line(boxFooter(width));
181
184
  };
@@ -277,6 +280,7 @@ export async function runHub({ workspaceRoot, feedFile, loadState, watchedPaths
277
280
  summary,
278
281
  run_id: runId,
279
282
  workspace_root: workspaceRoot,
283
+ observer_telemetry: true,
280
284
  },
281
285
  });
282
286
  };
@@ -512,6 +516,13 @@ export async function runHub({ workspaceRoot, feedFile, loadState, watchedPaths
512
516
  recordInteractiveTransition(before, { source: "keyboard" });
513
517
  return;
514
518
  }
519
+ if (key.name === "a") {
520
+ const before = captureRuntimeState();
521
+ state.view = "adapters";
522
+ rerender();
523
+ recordInteractiveTransition(before, { source: "keyboard" });
524
+ return;
525
+ }
515
526
  if (key.name === "r") {
516
527
  const before = captureRuntimeState();
517
528
  refresh();
@@ -1,5 +1,5 @@
1
1
  export function completeCommand(input, runIds) {
2
- const commands = ["help", "quit", "refresh", "hub", "run", "inspect", "compare", "history", "select", "next", "prev", "focus", "post", "handoff", "todo", "ledger", "event"];
2
+ const commands = ["help", "quit", "refresh", "hub", "run", "inspect", "compare", "history", "adapters", "select", "next", "prev", "focus", "post", "handoff", "todo", "ledger", "event"];
3
3
  const parts = input.split(/\s+/);
4
4
  const token = parts.at(-1) ?? "";
5
5
  const options = parts.length > 1 ? [...runIds, ...commands] : [...commands, ...runIds];
@@ -107,6 +107,9 @@ export function executeCommand(command, state) {
107
107
  const compareTarget = positionals[0];
108
108
  return { patch: { view: "compare", compareTarget }, message: "Switched to compare view." };
109
109
  }
110
+ if (name === "adapters") {
111
+ return { patch: { view: "adapters" }, message: "Switched to adapters view." };
112
+ }
110
113
  if (name === "history") {
111
114
  return { patch: { view: "history" }, message: "Switched to history view." };
112
115
  }