@ishlabs/cli 0.20.0 → 0.22.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/lib/docs.js CHANGED
@@ -315,6 +315,8 @@ pick was wrong.
315
315
  - \`concepts/assignment\` — task definition syntax.
316
316
  - \`concepts/questionnaire\` — question types and timing.
317
317
  - \`concepts/run-verbs\` — when to use \`study run\` vs \`ask run\`.
318
+ - \`guides/slicing-results\` — filter / project \`study results\` by frame,
319
+ segment, turn, sentiment, assignment, step.
318
320
  - \`reference/billing-limits\` — \`maxStudiesPerProduct\` cap on study creation.
319
321
  - \`reference/credits\` — per-run credit cost & how to preview before dispatch.
320
322
  `;
@@ -633,7 +635,7 @@ Tunables (both modes):
633
635
  the parties signal the conversation is over.
634
636
 
635
637
  Pair-mode rules:
636
- - Each side needs **either** \`--profile-*\` (explicit IDs) **or**
638
+ - Each side needs **either** \`--group-a\` / \`--group-b\` (explicit IDs) **or**
637
639
  \`--role-criteria-*\` (filter the backend resolves). The two can also
638
640
  be combined — criteria then acts as validation on the explicit list.
639
641
  - When both sides use explicit \`--group-a\` / \`--group-b\`, they
@@ -655,7 +657,7 @@ Pair-mode rules:
655
657
  \`type\` field in \`--questionnaire\` / \`--questions\` manifests
656
658
  (\`single-choice\` ↔ \`single_choice\`).
657
659
  - Audiences are pinned to the iteration. \`ish study run\` refuses
658
- run-time people overrides (\`--profile\` / \`--sample\` / \`--all\` /
660
+ run-time people overrides (\`--person\` / \`--sample\` / \`--all\` /
659
661
  filters) on a pair iteration — change the peoples via
660
662
  \`ish iteration update <id> --details-json '{...}'\` instead.
661
663
  - \`--max-turns\` / \`--early-termination\` on \`ish study run\` override
@@ -851,6 +853,9 @@ ride along when present in the JSON forms.
851
853
 
852
854
  - \`concepts/study\` — assignments are immutable to the run; questionnaire is too.
853
855
  - \`concepts/questionnaire\` — the other half of the study definition.
856
+ - \`guides/slicing-results\` — slice the post-run envelope by step
857
+ (\`--step verify-email --group-by step\`), surface per-participant verdicts
858
+ inline, or restrict to the evidence interactions with \`--include-evidence\`.
854
859
  - \`reference/json-mode\` — how \`step_completion\` renders in lean vs --verbose.
855
860
  `;
856
861
  const CONCEPT_QUESTIONNAIRE = `# concept: questionnaire
@@ -1127,7 +1132,7 @@ deleted ask was the active one.
1127
1132
  - \`concepts/round\` — what a round is and how it executes.
1128
1133
  - \`concepts/people\` — how participants are chosen at ask creation.
1129
1134
  - \`concepts/run-verbs\` — \`ish ask run\` vs \`ish study run\`.
1130
- - \`reference/credits\` — ask rounds bill \`n_participants * (1 + len(questions))\` credits per round; \`questions\` follow-ups bill *per participant* on top of the base response, so a 3-person panel with 2 follow-up questions costs \`3 * (1 + 2) = 9\` credits when all complete (not 3).
1135
+ - \`reference/credits\` — ask rounds bill **one credit per successful participant per round**, regardless of how many \`questions\` were included. The backend's asks worker bills \`amount=succeeded\` once per round dispatch; questions and round-summary synthesis don't trigger separate debits. A 3-person panel with 2 follow-up questions costs \`3\` credits when all complete, the same as a no-questions run. Failed participant responses (pre-flight errors, refusals) don't bill.
1131
1136
  `;
1132
1137
  const CONCEPT_ROUND = `# concept: round
1133
1138
 
@@ -1169,7 +1174,7 @@ const CONCEPT_PROFILE = `# concept: person
1169
1174
  A **person** is a reusable persona — the simulated
1170
1175
  human whose behaviour drives a participant instance during a study or ask.
1171
1176
 
1172
- - Alias prefix: \`tp-\`
1177
+ - Alias prefix: \`p-\`
1173
1178
  - Lives at the workspace level, reusable across studies and asks.
1174
1179
  - Distinct from a "participant" (\`pt-\`) — a participant is one *instance* of a
1175
1180
  profile inside one iteration.
@@ -1331,7 +1336,7 @@ A **source** is an input to \`ish person generate\`: a transcript,
1331
1336
  audio file, image, or PDF that an LLM reads to ground generated profiles
1332
1337
  in real customer evidence.
1333
1338
 
1334
- - Alias prefix: \`tps-\`
1339
+ - Alias prefix: \`ps-\`
1335
1340
  - Source kinds: \`text_file | audio | image\` (auto-detected from extension; \`text-file\` is accepted as a hyphen variant).
1336
1341
  - Audio supports speaker diarization via \`--diarize\`.
1337
1342
 
@@ -1401,7 +1406,7 @@ flags. Two ways to select:
1401
1406
  \`platform\` until the next release with a server-side
1402
1407
  deprecation warning)
1403
1408
 
1404
- The two modes are **mutually exclusive** — pass either \`--profile\` or
1409
+ The two modes are **mutually exclusive** — pass either \`--person\` or
1405
1410
  the filter set, not both.
1406
1411
 
1407
1412
  ## Empty-pool suggestions
@@ -1653,7 +1658,7 @@ and what they target differ.
1653
1658
  | Default | latest iteration of the active study | append a round to the active ask |
1654
1659
  | Fresh setup | \`ish iteration create …\` first, then run | \`--new\` (creates ask + round 1 in one shot) |
1655
1660
  | Specific target| \`--iteration <id>\` | positional ask id (\`a-6ec\`) |
1656
- | Audience | \`--profile\` OR filters with \`--sample\`/\`--all\` — else reuse iteration's participants | only at \`--new\`; fixed for the ask afterwards |
1661
+ | Audience | \`--person\` OR filters with \`--sample\`/\`--all\` — else reuse iteration's participants | only at \`--new\`; fixed for the ask afterwards |
1657
1662
  | Output unit | per-participant interactions + questionnaire answers | per-participant reactions per round |
1658
1663
 
1659
1664
  ## Decision rule
@@ -1741,7 +1746,7 @@ When extend is **not** the right verb:
1741
1746
  - Source participant is still RUNNING. \`cancel\` it first, then extend.
1742
1747
  Extend refuses non-terminal sources server-side.
1743
1748
  - You want a fresh cohort with new people flags. Use \`study run\`
1744
- with \`--profile\` / \`--sample\` / \`--all\` instead — extend is a
1749
+ with \`--person\` / \`--sample\` / \`--all\` instead — extend is a
1745
1750
  per-participant resume, not a batch op.
1746
1751
  - You want to change the iteration's URL or content. Edit the iteration
1747
1752
  itself (\`iteration update\` or a fresh iteration) — extend always
@@ -1901,8 +1906,8 @@ time the CLI sees an entity.
1901
1906
  - \`s-\` study
1902
1907
  - \`i-\` iteration
1903
1908
  - \`pt-\` participant (instance of a person in an iteration)
1904
- - \`tp-\` person
1905
- - \`tps-\` person source
1909
+ - \`p-\` person
1910
+ - \`ps-\` person source
1906
1911
  - \`a-\` ask
1907
1912
  - \`r-\` ask round
1908
1913
  - \`c-\` config (simulation config)
@@ -2418,7 +2423,7 @@ not branch on \`status: 0\` — that value is never emitted as of 0.20.
2418
2423
  - Lists print as JSON arrays (or paginated wrappers). Single resources
2419
2424
  as JSON objects.
2420
2425
  - Field names match the underlying API resource (snake_case).
2421
- - Aliases (\`s-…\`, \`a-…\`, \`tp-…\`, …) appear alongside UUIDs in
2426
+ - Aliases (\`s-…\`, \`a-…\`, \`p-…\`, …) appear alongside UUIDs in
2422
2427
  \`--verbose\` mode and replace UUIDs in default lean mode.
2423
2428
 
2424
2429
  ## Examples
@@ -2461,6 +2466,210 @@ ish study results --human
2461
2466
  When you genuinely need multiple fields in one parse pass, \`--json\` is
2462
2467
  still the right tool — \`--get\` is for single-value capture, not for
2463
2468
  reshaping output.
2469
+
2470
+ ## Slicing study results
2471
+
2472
+ \`ish study results <id>\` accepts filter flags (\`--frame\`, \`--segment\`,
2473
+ \`--turn\`, \`--side\`, \`--assignment\`, \`--step\`, \`--sentiment\`,
2474
+ \`--actor\`, \`--iteration\`, \`--participant\`) and projection flags
2475
+ (\`--group-by iteration|frame|segment|turn|assignment|step\`). When any
2476
+ filter is passed on the default \`study results\` envelope, the envelope
2477
+ gains a \`totals_unfiltered\` field (\`{participant_count,
2478
+ interaction_count}\`) so an agent can sanity-check coverage: "matched
2479
+ 12 / 80 participants". A zero-match filter returns the stable envelope
2480
+ with \`participant_count: 0\` and exit code **0** (not 4) — slicing
2481
+ never errors on no-match. \`--group-by\` returns a different shape — a
2482
+ uniform envelope \`{axis, rows, totals_unfiltered, modality_warnings,
2483
+ study_id, modality}\` (see \`guides/slicing-results\`).
2484
+
2485
+ \`--group-by\` is **router-gated by modality**: \`frame\` requires
2486
+ interactive, \`segment\` requires media (video / audio / text / document),
2487
+ \`turn\` requires chat. Mismatched filter flags (e.g. \`--segment 0\` on
2488
+ an interactive study) emit a stderr warning and are ignored — they
2489
+ don't error. Full worked examples in \`guides/slicing-results\`.
2490
+ `;
2491
+ const GUIDE_SLICING_RESULTS = `# guide: slicing study results
2492
+
2493
+ \`ish study results <id>\` returns a kitchen-sink envelope by default
2494
+ (every participant, every interaction, every interview answer). For
2495
+ narrower questions — *"what differed on the login screen across these
2496
+ five iterations?"*, *"who failed verify-email, and why?"*, *"frustrated
2497
+ reactions to segment 3 of the video"* — \`ish study results\` accepts
2498
+ **filter flags** (which interactions to keep) and **projection flags**
2499
+ (how to roll up what survives). Filters compose with AND across flags
2500
+ and OR within \`--sentiment\`. Filters and projections are pure
2501
+ client-side; no extra round trip beyond the standard study fetch.
2502
+
2503
+ ## Filter flags
2504
+
2505
+ | Flag | Matches | Where it applies |
2506
+ |-------------------------------|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------|
2507
+ | \`--frame <ref>\` | Interactions whose Frame name contains \`<ref>\` (case-insensitive). Also accepts a full Frame UUID, an \`f-…\` alias, or a \`frame_version_id\` UUID. | interactive — warn + ignore on chat / media |
2508
+ | \`--segment <ref>\` | Integer matches \`actions[0].data.segment_index\`; non-integer is a substring match against \`segment_label\`. | video, audio, text, document — warn + ignore elsewhere |
2509
+ | \`--turn <n>\` | Interactions whose \`actions[0].data.turn_index == n\`. | chat (external_chatbot + participant_pair) |
2510
+ | \`--side <a\|b>\` | Interactions whose parent assignment has \`side == a\` or \`side == b\`. | chat participant_pair — warn + ignore on other chat / non-chat |
2511
+ | \`--assignment <ref>\` | Assignment UUID, or substring match against the assignment name. | all |
2512
+ | \`--step <ref>\` | Filters \`participant_assignments[].step_results[]\` to verdicts matching the step id or name. | interactive + external_chatbot chat (steps live there) |
2513
+ | \`--sentiment <labels>\` | Comma-separated, case-insensitive label list (repeatable). Drops null-sentiment rows. | all |
2514
+ | \`--actor <ai\|human\|user>\` | Restrict by actor. | all |
2515
+ | \`--iteration <ref>\` | Iteration UUID, iteration alias (\`i-…\`), or label (\`A\`, \`B\`, … case-insensitive). | all |
2516
+ | \`--participant <ref>\` | Participant UUID or \`pt-…\` alias. | all |
2517
+ | \`--include-unmatched\` | With \`--frame\`, keep degraded captures (\`frame_version_id: null\`) under a synthetic \`_unmatched\` bucket instead of dropping them. | interactive |
2518
+ | \`--include-evidence\` | With \`--step\`, also drop interactions not listed in any surviving \`step_results[].evidence_interaction_ids[]\`. | interactive + external_chatbot chat |
2519
+
2520
+ **Modality mismatch is not an error.** Pass \`--segment 0\` on an
2521
+ interactive study and the filter is ignored with a stderr warning.
2522
+ The exception is \`--group-by\` — see below.
2523
+
2524
+ ## Projection flags (--group-by)
2525
+
2526
+ Every \`--group-by\` axis returns the same envelope:
2527
+ \`{axis, rows, totals_unfiltered, modality_warnings, study_id, modality}\`.
2528
+ Top-level \`axis\` echoes the requested axis; \`study_id\` is the \`s-…\`
2529
+ alias; \`modality\` echoes the study's modality. \`rows\` is an
2530
+ axis-specific array of slice objects (see the table below for the per-row
2531
+ shape). \`modality_warnings\` carries any filter-flag mismatches
2532
+ (e.g. \`--turn\` on a non-chat study); empty array when none.
2533
+
2534
+ | Axis | Row shape (one element of \`rows[]\`) | Modality |
2535
+ |-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
2536
+ | \`iteration\` | \`{iteration_id, iteration_label, participant_count, interaction_count, sentiment, sample_comments, top_actions}\` | all |
2537
+ | \`frame\` | \`{frame_id, frame_label, interaction_count, sentiment_histogram, sample_comments, participant_aliases}\` | interactive (router errors on non-interactive) |
2538
+ | \`segment\` | \`{segment_index, segment_label, interaction_count, sentiment_histogram, engagement_histogram, sample_comments}\` | media (router errors on non-media) |
2539
+ | \`turn\` | \`{turn_index, interaction_count, sentiment_histogram, sample_replies, failures}\` | chat (router errors on non-chat) |
2540
+ | \`assignment\` | \`{assignment_id, assignment_name, interaction_count, sentiment_histogram, step_completion}\` | all |
2541
+ | \`step\` | \`{assignment_id, assignment_name, step_id, step_name, total, passed, inconclusive, failed, rate, participant_verdicts: [{participant_alias, verdict, reason, evidence_interaction_ids}]}\` | interactive + external_chatbot chat |
2542
+
2543
+ \`--group-by\` is **mutually exclusive with \`--summary\` and
2544
+ \`--transcript\`**. \`--group-by frame\` on a chat study, \`--group-by
2545
+ turn\` on a video study, etc. error at the surface (exit 2) with a
2546
+ clear message before any IO. The error envelope includes a \`hint\`
2547
+ field naming the axis that DOES apply to the study's modality
2548
+ (\`use --group-by segment\` on audio/video/text/document, \`use --group-by
2549
+ turn\` on chat, \`use --group-by frame\` on interactive) — agents can
2550
+ branch on it to retry productively in one hop.
2551
+
2552
+ ## The empty-slice contract
2553
+
2554
+ A filter combination that matches zero interactions returns the
2555
+ **uniform envelope** with:
2556
+
2557
+ - \`rows: []\`
2558
+ - \`totals_unfiltered: {participant_count: <N>, interaction_count: <M>}\` populated
2559
+ - \`axis\`, \`study_id\`, \`modality\` still populated
2560
+ - exit code **0** (not 4)
2561
+
2562
+ \`totals_unfiltered\` is the agent's sanity check: *"my filter matched
2563
+ 0 of 80 participants — is the filter too tight, or did the run not
2564
+ produce data?"*. The shape never collapses to \`null\` or a different
2565
+ envelope; \`--get participant_count\` is always safe on the default
2566
+ (non-\`--group-by\`) envelope.
2567
+
2568
+ The default+filter envelope (no \`--group-by\`) also carries
2569
+ \`modality_warnings: string[]\` — any filter flags that were dropped as
2570
+ off-modality (e.g. \`--turn 1\` on an interactive study) appear here.
2571
+ Agents piping stderr to \`/dev/null\` get the same signal on stdout.
2572
+
2573
+ ## Worked examples
2574
+
2575
+ \`\`\`bash
2576
+ # What differed on the login screen across the five iterations?
2577
+ ish study results s-b2c --frame login --group-by iteration
2578
+
2579
+ # Frustrated reactions to segment 3 of the video
2580
+ ish study results s-b2c --segment 3 --sentiment Frustrated
2581
+
2582
+ # Who failed the "verify email" step, and why?
2583
+ ish study results s-b2c --assignment "Sign up" --step verify-email --group-by step
2584
+
2585
+ # Chat participant_pair: only side A turn 4
2586
+ ish study results s-b2c --side a --turn 4
2587
+
2588
+ # Surface degraded captures (frame_version_id: null) under a "_unmatched" bucket:
2589
+ ish study results s-b2c --frame login --include-unmatched --group-by frame
2590
+
2591
+ # Narrow the lean summary to a slice:
2592
+ ish study results s-b2c --summary --frame checkout --json
2593
+ \`\`\`
2594
+
2595
+ ## Combining filters
2596
+
2597
+ Filters compose with **AND across flags** and **OR within
2598
+ \`--sentiment\`**. \`--frame login --sentiment Frustrated,Confused\`
2599
+ means "interactions on the login frame whose sentiment is Frustrated
2600
+ OR Confused". \`--summary\` is orthogonal to filters and narrows the
2601
+ summary over the filtered set. \`--transcript\` is single-participant
2602
+ and **errors when any filter or \`--group-by\` is set** (exit 2).
2603
+
2604
+ ## Defensive handling of nullable fields
2605
+
2606
+ - \`interaction.sentiment\` is nullable (chat failure stubs,
2607
+ pre-sentiment rows). Dropped **only** when \`--sentiment\` is set; kept
2608
+ by every other filter.
2609
+ - \`interaction.frame_version_id\` is nullable on interactive studies
2610
+ (degraded captures, ~12% on a failing iteration). Dropped by
2611
+ \`--frame\` unless \`--include-unmatched\` is passed; surfaced as a
2612
+ \`_unmatched\` bucket in \`--group-by frame\`.
2613
+ - Chat \`bot_reply.failure\` rows are kept in the default envelope,
2614
+ dropped by \`--sentiment\` (they have \`sentiment: null\`), kept by
2615
+ \`--actor\`, visible in \`--group-by turn\` under a \`failures\`
2616
+ counter.
2617
+
2618
+ ## --frame resolution
2619
+
2620
+ \`--frame login\` walks the frame list returned by
2621
+ \`GET /studies/{id}/frames\` and matches **case-insensitive substring**
2622
+ against the frame name. Other accepted shapes:
2623
+
2624
+ - \`--frame 6ec…\` — full Frame UUID (exact match)
2625
+ - \`--frame f-6ec\` — short alias resolved via \`alias-store\`
2626
+ - \`--frame 7ec…\` — a \`frame_version_id\` UUID (matches only that version)
2627
+
2628
+ Ambiguous substring (matches >1 frame) errors with the candidate list:
2629
+
2630
+ \`\`\`
2631
+ ish study results s-b2c --frame log
2632
+ # Error: --frame "log" is ambiguous — matched 2 frames: Login, Logout.
2633
+ # Use a more specific substring, a full Frame UUID, or an \`f-…\` alias.
2634
+ \`\`\`
2635
+
2636
+ No match at all errors and lists the available frame names.
2637
+
2638
+ ## Common --get paths on a sliced envelope
2639
+
2640
+ \`\`\`
2641
+ # Sanity-check coverage:
2642
+ --get axis
2643
+ --get study_id
2644
+ --get modality
2645
+ --get totals_unfiltered.participant_count
2646
+ --get totals_unfiltered.interaction_count
2647
+ --get modality_warnings
2648
+
2649
+ # Per-iteration projection rows:
2650
+ --get rows.iteration_label # one label per line
2651
+ --get rows.0.participant_count
2652
+ --get rows.0.sentiment
2653
+
2654
+ # Per-frame / per-segment / per-turn (rows[] is the axis array):
2655
+ --get rows.0.frame_label
2656
+ --get rows.0.segment_index
2657
+ --get rows.0.sentiment_histogram
2658
+
2659
+ # Per-step:
2660
+ --get rows.0.rate
2661
+ --get rows.0.participant_verdicts.verdict
2662
+ \`\`\`
2663
+
2664
+ ## Related
2665
+
2666
+ - \`concepts/study\` — the parent artifact whose results are being sliced.
2667
+ - \`concepts/assignment\` — defines the steps that \`--step\` and
2668
+ \`--group-by step\` filter against.
2669
+ - \`reference/json-mode\` — display vs capture vs chain output rules
2670
+ (\`--get\`, \`--fields\`, exit codes).
2671
+ - \`reference/aliases\` — \`s-…\` for studies, \`pt-…\` for participants,
2672
+ \`f-…\` for frames. Any UUID-accepting flag also accepts the alias.
2464
2673
  `;
2465
2674
  const GUIDE_FIRST_STUDY = `# guide: your first study, end to end
2466
2675
 
@@ -2830,6 +3039,8 @@ free credits before re-dispatch.
2830
3039
  estimate at preview time — the CLI prints the shape (\`N × … × 2\`)
2831
3040
  instead of a number.
2832
3041
 
3042
+ **Naming note:** "tier" in ish means **billing** tier (FREE / STARTER / PRO / ENTERPRISE — a credit-budget knob). It is NOT a simulation-quality dial. Per-run simulation behaviour (model, timing, retries) is controlled via \`ish config\` — see \`ish config --help\`. \`docs search tier\` returns billing results by design.
3043
+
2833
3044
  ## Related
2834
3045
 
2835
3046
  - \`reference/billing-limits\` — per-tier *entity* caps (max
@@ -3264,13 +3475,13 @@ Optional \`--max-turns <n>\` (default 12) caps the chat per participant.
3264
3475
 
3265
3476
  Audience size is set at run time for **external_chatbot** chat
3266
3477
  studies. Use \`--sample <N>\` to pick N random simulatable profiles,
3267
- or \`--all\` for the full pool. \`--profile <id>\` is also supported
3478
+ or \`--all\` for the full pool. \`--person <ids>\` is also supported
3268
3479
  for explicit selection:
3269
3480
  \`\`\`
3270
3481
  ish study run stu-xyz --sample 5 --wait
3271
3482
  \`\`\`
3272
3483
 
3273
- > **Pair-mode is different.** \`--sample\` / \`--profile\` / demographic
3484
+ > **Pair-mode is different.** \`--sample\` / \`--person\` / demographic
3274
3485
  > filters on \`study run\` are **refused** for participant_pair iterations
3275
3486
  > — pair groups live on the iteration itself. Set them at
3276
3487
  > iteration-create time via \`--group-a/-b\` (with 1×N broadcast)
@@ -3426,7 +3637,7 @@ Keys (all optional): \`occupation\`, \`min_age\`, \`max_age\`,
3426
3637
  \`requires_captions\`, \`uses_screen_reader\`, \`prefers_reduced_motion\`,
3427
3638
  \`prefers_high_contrast\`, \`has_any_accessibility_need\`. The five \`*_in\`
3428
3639
  arrays accept snake_case spec values; the five accessibility filters are
3429
- booleans. Combine \`--profile-*\` and \`--role-criteria-*\` on the same side
3640
+ booleans. Combine \`--group-a\` / \`--group-b\` and \`--role-criteria-*\` on the same side
3430
3641
  to make criteria validate an explicit list (mismatch blocks the run).
3431
3642
 
3432
3643
  MECE notes for the list filters:
@@ -3812,7 +4023,7 @@ cap at 40 entries.
3812
4023
  - \`concepts/person\` — what a person is; structured fields.
3813
4024
  - \`concepts/source\` — interview transcripts / audio / PDF inputs
3814
4025
  for the people-generation flow.
3815
- - \`reference/aliases\` — \`tp-…\` is the profile alias prefix.
4026
+ - \`reference/aliases\` — \`p-…\` is the person alias prefix.
3816
4027
  `;
3817
4028
  const GUIDE_MCP_ADD = `# guide: wire ish into your AI clients (\`ish mcp add\`)
3818
4029
 
@@ -4053,6 +4264,12 @@ const PAGES = [
4053
4264
  description: "Login → workspace → people → study → iteration → run → results.",
4054
4265
  body: GUIDE_FIRST_STUDY,
4055
4266
  },
4267
+ {
4268
+ slug: "guides/slicing-results",
4269
+ title: "guide: slicing study results by frame / segment / turn / sentiment",
4270
+ description: "Filter and project `ish study results` — --frame, --segment, --turn, --side, --assignment, --step, --sentiment, --actor, --iteration, --participant; --group-by iteration|frame|segment|turn|assignment|step; totals_unfiltered + empty-slice contract.",
4271
+ body: GUIDE_SLICING_RESULTS,
4272
+ },
4056
4273
  {
4057
4274
  slug: "guides/chat",
4058
4275
  title: "guide: chat-modality studies",
@@ -35,10 +35,16 @@ export declare function outputList(rows: unknown[], json: boolean): void;
35
35
  /**
36
36
  * Error with valid options — used for content_type and similar validation.
37
37
  * Surfaces valid_options in JSON so agents can self-correct.
38
+ *
39
+ * Optional `hint` is the agent's *actionable next step* (e.g. for a wrong
40
+ * --group-by axis on the current modality, the axis that DOES apply). Distinct
41
+ * from `valid_options`, which describes where the supplied value WOULD be
42
+ * valid. Both serialize into the error envelope when present.
38
43
  */
39
44
  export declare class ValidationError extends Error {
40
45
  valid_options: string[];
41
- constructor(message: string, valid_options: string[]);
46
+ hint?: string | undefined;
47
+ constructor(message: string, valid_options: string[], hint?: string | undefined);
42
48
  }
43
49
  export declare function outputError(err: unknown, json: boolean): void;
44
50
  export declare function printTable(headers: string[], rows: string[][]): void;
@@ -48,6 +54,12 @@ export declare function formatWorkspaceDetail(workspace: Record<string, unknown>
48
54
  export declare function formatSiteAccessStatus(summary: import("./site-access.js").SiteAccessSummary, json: boolean): void;
49
55
  export declare function formatStudyList(studies: Record<string, unknown>[], json: boolean): void;
50
56
  export declare function formatStudyDetail(study: Record<string, unknown>, json: boolean, options?: OutputOptions, participants?: ReadonlyArray<Record<string, unknown>>): void;
57
+ /**
58
+ * Stable JSON envelope for `study results`. Schema is fixed regardless of
59
+ * study state — fields default to `null`, `0`, or `[]` when nothing has run.
60
+ * Agents can rely on the keys always being present (M4).
61
+ */
62
+ export declare function buildStudyResultsEnvelope(study: Record<string, unknown>, participants: ReadonlyArray<Record<string, unknown>>): Record<string, unknown>;
51
63
  export declare function formatStudyResults(study: Record<string, unknown>, participants: ReadonlyArray<Record<string, unknown>>, json: boolean): void;
52
64
  /**
53
65
  * `study results --summary` projection. Drops interview_answers + per-participant
@@ -102,3 +114,14 @@ export declare function deriveWinnerConfidence(args: {
102
114
  }): "low" | "medium" | "high";
103
115
  export declare function formatAskResults(ask: Record<string, unknown>, json: boolean, roundFilter?: number): void;
104
116
  export declare function formatConfigList(configs: Record<string, unknown>[], json: boolean): void;
117
+ export type StudyResultsGroupByKind = "iteration" | "frame" | "segment" | "turn" | "assignment" | "step";
118
+ /**
119
+ * Render a `--group-by <kind>` projection wrapped in the uniform
120
+ * `SliceResponse` envelope (`{ axis, rows, totals_unfiltered,
121
+ * modality_warnings, study_id, modality }`). JSON mode is a thin
122
+ * pass-through to jsonOutput with `preProjected: true` so the lean
123
+ * transform doesn't strip our stable empties. Human mode pulls slices
124
+ * out of `rows` and renders one section per slice plus a small ASCII
125
+ * sentiment histogram.
126
+ */
127
+ export declare function formatStudyResultsGroupBy(projection: unknown, kind: StudyResultsGroupByKind, json: boolean): void;