humanish 0.40.0 → 0.41.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.
package/dist/run.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type CodexAppServerTrace } from "./codex-app-server.js";
2
- import { type ActorTrace } from "./actor-contract.js";
2
+ import { type ActorStatus, type ActorTrace } from "./actor-contract.js";
3
3
  import { type CapturedGitState } from "./core/git-state.js";
4
4
  import type { E2BDesktopModule } from "./e2b-desktop-launch.js";
5
5
  import { type PreparedRunArtifactPaths } from "./run-paths.js";
@@ -807,12 +807,65 @@ export interface RunRerunLineage {
807
807
  completionReason?: string;
808
808
  }>;
809
809
  }
810
+ /**
811
+ * What happened to the PARTICIPANTS in a study, with the denominator attached.
812
+ *
813
+ * A stakeholder watching through the glass forms conclusions from vivid moments — that is the
814
+ * classic failure of the viewing room, and it is why researchers synthesize rather than letting the
815
+ * room decide. So anything shown to a stakeholder carries its count, or it becomes a machine for
816
+ * manufacturing certainty from n=1 (docs/principles/three-roles.md).
817
+ *
818
+ * These are OUTCOMES, not scores. `abandoned` is the most valuable thing a usability study
819
+ * produces, and `harnessFailed` is the only member that says the instrument, rather than the
820
+ * product, is what went wrong.
821
+ */
822
+ export interface ParticipantOutcomes {
823
+ /** Participants whose sessions reached a terminal state — the denominator for every count below. */
824
+ total: number;
825
+ /** Reached the goal. */
826
+ reachedGoal: number;
827
+ /** Stopped trying. A finding about the product. */
828
+ abandoned: number;
829
+ /** Ran out of session or budget before reaching the goal. */
830
+ ranOut: number;
831
+ /** Needed an approval the run could not give. */
832
+ blocked: number;
833
+ /** The harness failed them: a dead sandbox, a provider error, a broken artifact. */
834
+ harnessFailed: number;
835
+ /**
836
+ * Participants who reported friction or a defect on the way, whatever their outcome.
837
+ *
838
+ * This is NOT a failure count and it overlaps the others on purpose — someone can reach the goal
839
+ * and still tell you the road there was broken. A live two-persona run made the case: both
840
+ * participants signed in, so "2/2 reached the goal" was true, and the keyboard-first one also
841
+ * reported that the signature step could not be completed without a mouse. Reporting only the
842
+ * outcome would have buried the single most useful thing that run produced.
843
+ */
844
+ reportedFriction: number;
845
+ }
810
846
  export interface ReviewSummary {
811
847
  schema: typeof REVIEW_SCHEMA;
812
848
  verdict: "contract_proof_only" | "pass" | "fail" | "blocked" | "timed_out";
813
849
  summary: string;
814
850
  gaps: string[];
851
+ /**
852
+ * The study result, separate from the verdict above.
853
+ *
854
+ * `verdict` answers a gate-shaped question and has to collapse a run to one word. This answers
855
+ * the research question — what happened to the people in the study — and does not collapse: a run
856
+ * where two of three participants finished is not usefully "fail", and a run where the harness
857
+ * broke is a different thing from one where a persona gave up. Absent on a dry-run contract
858
+ * bundle, which has no participants.
859
+ */
860
+ participants?: ParticipantOutcomes;
815
861
  }
862
+ /** Tally participant outcomes from actor statuses. Statuses this does not recognise are counted in
863
+ * `total` but nowhere else, so the parts can never exceed the whole. */
864
+ export declare function tallyParticipantOutcomes(statuses: readonly ActorStatus[],
865
+ /** Per-participant: did this one report friction or a defect? Same order as `statuses`. */
866
+ reportedFriction?: readonly boolean[]): ParticipantOutcomes;
867
+ /** One line a stakeholder can read, with the denominator attached to every number. */
868
+ export declare function formatParticipantOutcomes(outcomes: ParticipantOutcomes): string;
816
869
  export declare function buildRunSource(args: {
817
870
  cwd: string;
818
871
  capturedAt?: Date | string;
package/dist/run.js CHANGED
@@ -49,6 +49,53 @@ const SAFE_GIT_NOTES = new Set([
49
49
  "public-safe synthetic fixture",
50
50
  "public-safe synthetic OSS meta-lab fixture"
51
51
  ]);
52
+ /** Tally participant outcomes from actor statuses. Statuses this does not recognise are counted in
53
+ * `total` but nowhere else, so the parts can never exceed the whole. */
54
+ export function tallyParticipantOutcomes(statuses,
55
+ /** Per-participant: did this one report friction or a defect? Same order as `statuses`. */
56
+ reportedFriction = []) {
57
+ const tally = {
58
+ total: statuses.length,
59
+ reachedGoal: 0,
60
+ abandoned: 0,
61
+ ranOut: 0,
62
+ blocked: 0,
63
+ harnessFailed: 0,
64
+ reportedFriction: reportedFriction.filter(Boolean).length
65
+ };
66
+ for (const status of statuses) {
67
+ if (status === "passed")
68
+ tally.reachedGoal += 1;
69
+ else if (status === "abandoned")
70
+ tally.abandoned += 1;
71
+ else if (status === "incomplete" || status === "timed_out")
72
+ tally.ranOut += 1;
73
+ else if (status === "blocked")
74
+ tally.blocked += 1;
75
+ else if (status === "failed")
76
+ tally.harnessFailed += 1;
77
+ }
78
+ return tally;
79
+ }
80
+ /** One line a stakeholder can read, with the denominator attached to every number. */
81
+ export function formatParticipantOutcomes(outcomes) {
82
+ if (outcomes.total === 0)
83
+ return "no participants reached a terminal state";
84
+ const parts = [`${outcomes.reachedGoal}/${outcomes.total} reached the goal`];
85
+ if (outcomes.abandoned > 0)
86
+ parts.push(`${outcomes.abandoned} gave up`);
87
+ if (outcomes.ranOut > 0)
88
+ parts.push(`${outcomes.ranOut} ran out of session`);
89
+ if (outcomes.blocked > 0)
90
+ parts.push(`${outcomes.blocked} blocked on an approval`);
91
+ if (outcomes.harnessFailed > 0)
92
+ parts.push(`${outcomes.harnessFailed} lost to a harness failure`);
93
+ // Last, and separate, because it cuts across the outcomes rather than partitioning them: someone
94
+ // can reach the goal and still have found the road there broken.
95
+ if (outcomes.reportedFriction > 0)
96
+ parts.push(`${outcomes.reportedFriction} reported friction`);
97
+ return parts.join(", ");
98
+ }
52
99
  export async function buildRunSource(args) {
53
100
  const gitOptions = args.capturedAt === undefined ? {} : { capturedAt: args.capturedAt };
54
101
  return {