@moda-ai/cli 1.35.0 → 1.36.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/cli.js CHANGED
@@ -234,6 +234,46 @@ var DetectionReviewSchema = z.object({
234
234
  verdict: z.enum(DETECTION_VERDICTS),
235
235
  note: z.string().max(2000).optional()
236
236
  });
237
+ var FALSE_POSITIVE_KINDS = ["problem", "problem-detection", "quote", "tool", "laziness", "hallucination", "custom-signal"];
238
+ var FalsePositiveSchema = z.object({
239
+ kind: z.enum(FALSE_POSITIVE_KINDS),
240
+ target: z.string().min(1).max(255),
241
+ undo: z.boolean(),
242
+ reason: z.string().max(2000).optional(),
243
+ conversation_id: z.string().min(1).max(255).optional(),
244
+ attribution_id: z.string().regex(UUID_RE, "Attribution id must be a UUID").optional(),
245
+ family: z.string().regex(/^[a-z_]{1,64}$/, "Family must be a lowercase emotion family").optional(),
246
+ turn: z.preprocess((v) => typeof v === "string" && /^\d+$/.test(v.trim()) ? Number(v) : v, z.number().int().min(0).optional()),
247
+ quote: z.string().min(1).max(4000).optional(),
248
+ tool: z.string().min(1).max(255).optional(),
249
+ tool_use_id: z.string().min(1).max(255).optional(),
250
+ msg_index: z.preprocess((v) => typeof v === "string" && /^\d+$/.test(v.trim()) ? Number(v) : v, z.number().int().min(0).optional()),
251
+ msg_dedup_token: z.string().min(1).max(200).optional()
252
+ }).superRefine((v, ctx) => {
253
+ const need = (ok, message) => {
254
+ if (!ok)
255
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message });
256
+ };
257
+ if (v.kind === "problem") {
258
+ need(UUID_RE.test(v.target), "Problem id must be a canonical UUID");
259
+ need(!v.undo, "A problem dismissal cannot be undone from the CLI");
260
+ }
261
+ if (v.kind === "problem-detection") {
262
+ need(UUID_RE.test(v.target), "Problem id must be a canonical UUID");
263
+ need(v.conversation_id, "--conversation-id is required for problem-detection");
264
+ need(v.attribution_id, "--attribution-id is required for problem-detection");
265
+ }
266
+ if (v.kind === "quote") {
267
+ need(v.family && v.turn !== undefined && v.quote, "--family, --turn and --quote are required for quote (copy them from moda emotions)");
268
+ }
269
+ if (v.kind === "tool") {
270
+ need(v.tool, "--tool is required for tool (the failing tool name)");
271
+ need(v.tool_use_id || v.msg_index !== undefined, "--tool-use-id or --msg-index is required for tool (from moda tool-failure-detail)");
272
+ }
273
+ if (v.kind === "custom-signal") {
274
+ need(v.conversation_id, "--conversation-id is required for custom-signal");
275
+ }
276
+ });
237
277
  var EMOTION_FAMILIES = [
238
278
  "frustration",
239
279
  "sadness",
@@ -255,6 +295,13 @@ var HallucinationsSchema = z.object({
255
295
  conversation_id: z.string().max(200).optional(),
256
296
  kind: z.enum(["contradicted", "verified"]).optional()
257
297
  });
298
+ var LazinessSchema = z.object({
299
+ days_back: z.number().min(1).max(90).default(7).optional(),
300
+ limit: z.number().min(1).max(50).default(10).optional(),
301
+ offset: z.number().min(0).max(1e4).optional(),
302
+ conversation_id: z.string().max(200).optional(),
303
+ family: z.string().regex(/^[a-z_]{1,64}$/, "Family must be a lowercase laziness family").optional()
304
+ });
258
305
  var StepScoresSchema = z.object({
259
306
  conversation_id: z.string().min(1)
260
307
  });
@@ -5401,12 +5448,15 @@ Commands:
5401
5448
  frustrations Get user frustration detections (legacy single-family; see emotions)
5402
5449
  emotions Multi-family emotion detections (frustration, sadness, confusion, anxiety, trust, positive)
5403
5450
  hallucinations Grounding detections: contradicted/verified outputs with rule breakdown
5451
+ laziness Laziness detections per trace (repeated calls, premature handoffs, ...)
5404
5452
  tool-failures Get tool failure overview
5405
5453
  tool-failure-detail <tool_name> Get per-tool failure detail
5406
5454
  problems Rank cross-signal Problems by root cause (what to fix first)
5407
5455
  problem-impact Live problem/use-case impact (--node-id, --problem-id, --mode, --days-back)
5408
5456
  problem <problem_id> Open one Problem: dossier, or --evidence/--reports/--traces/--feedback
5409
5457
  problem-feedback <problem_id> Mark a Problem fixed, dismiss, rename, or flag a bad attribution
5458
+ false-positive <kind> <target> Mark any detection as a false positive, optional --reason (problem, problem-detection, quote, tool, laziness, hallucination, custom-signal)
5459
+ false-positives [conv_id] Current false-positive marks: all (--kind, --all, --limit) or one trace
5410
5460
  detection-reviews <conv_id> Problem detections on a trace with their review state
5411
5461
  detection-review <problem_id> Mark a problem detection correct, a false positive, or clear the review
5412
5462
  signals List custom signals (name, criteria, status, 7-day stats)
@@ -5516,6 +5566,11 @@ Emotions flags:
5516
5566
  --days-back=N --limit=N Window 1-90 (default 7); page size 1-20 (default 10)
5517
5567
  --offset=N Skip N detections (0-10000)
5518
5568
 
5569
+ Laziness flags:
5570
+ --family=F Narrow to one laziness family
5571
+ --conversation-id=ID One trace's detections
5572
+ --days-back=N --limit=N Window 1-90 (default 7); page size 1-50 (default 10)
5573
+
5519
5574
  Hallucinations flags:
5520
5575
  --kind=contradicted|verified Narrow the detections list
5521
5576
  --conversation-id=ID Scope summary + list to one trace ID
@@ -5529,10 +5584,21 @@ Problem flags (moda problem <id>):
5529
5584
 
5530
5585
  Problem-feedback flags:
5531
5586
  --action=A mark_fixed|dismiss|flag_attribution|rename (required)
5532
- --reason=TEXT Required for dismiss and flag_attribution
5587
+ --reason=TEXT Required for flag_attribution; optional for dismiss
5533
5588
  --attribution-id=UUID Required for flag_attribution (ids come from problem --evidence)
5534
5589
  --new-name=NAME Required for rename
5535
5590
 
5591
+ False-positive flags (moda false-positive <kind> <target>):
5592
+ --reason=TEXT Why it is wrong (optional)
5593
+ --undo Remove an earlier mark (every kind except problem)
5594
+ problem <problem_id> Dismiss the problem: hidden at once, logged for review, folded into reconcile
5595
+ problem-detection <problem_id> One detection on a trace; needs --conversation-id and --attribution-id; teaches the problem
5596
+ quote <conversation_id> A user quote; needs --family, --turn and --quote (from moda emotions); audit only
5597
+ tool <conversation_id> One failing tool call; needs --tool and --tool-use-id (or --msg-index); audit only
5598
+ laziness <conversation_id> Laziness on that trace; audit only
5599
+ hallucination <conversation_id> Hallucinations on that trace; audit only
5600
+ custom-signal <signal_id> A hit on that trace (--conversation-id); saved as does_not_fit so the signal learns
5601
+
5536
5602
  Detection-review flags (moda detection-review <problem_id>):
5537
5603
  --conversation-id=ID Trace the detection is on (required)
5538
5604
  --attribution-id=UUID Detection id (from problem --evidence or detection-reviews; required)
@@ -6404,7 +6470,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
6404
6470
  throw new CliInputError(`Problem id must be a canonical UUID; got "${params.id}".`);
6405
6471
  }
6406
6472
  const reason = params.reason?.trim() ?? "";
6407
- if ((params.action === "dismiss" || params.action === "flag_attribution") && reason === "") {
6473
+ if (params.action === "flag_attribution" && reason === "") {
6408
6474
  throw new CliInputError(`--reason is required for --action=${params.action}.`);
6409
6475
  }
6410
6476
  if (params.action === "rename" && (params.new_name?.trim() ?? "") === "") {
@@ -6538,6 +6604,99 @@ async function runCommand(command, positional, flags, positionals = positional ?
6538
6604
  context.output.writeData(data);
6539
6605
  break;
6540
6606
  }
6607
+ case "false-positive": {
6608
+ const usage = `Usage: moda false-positive <${FALSE_POSITIVE_KINDS.join("|")}> <target> --reason=TEXT [--undo]`;
6609
+ const [kind, target] = positionals;
6610
+ if (!kind || !target)
6611
+ throw new CliInputError("<kind> and <target> are required", usage);
6612
+ const params = FalsePositiveSchema.parse({
6613
+ kind,
6614
+ target,
6615
+ undo: flags.undo !== undefined && flags.undo !== "false",
6616
+ ...flags.reason !== undefined ? { reason: flags.reason } : {},
6617
+ ...flags["conversation-id"] !== undefined ? { conversation_id: flags["conversation-id"] } : {},
6618
+ ...flags["attribution-id"] !== undefined ? { attribution_id: flags["attribution-id"] } : {},
6619
+ ...flags.family !== undefined ? { family: flags.family } : {},
6620
+ ...flags.turn !== undefined ? { turn: flags.turn } : {},
6621
+ ...flags.quote !== undefined ? { quote: flags.quote } : {},
6622
+ ...flags.tool !== undefined ? { tool: flags.tool } : {},
6623
+ ...flags["tool-use-id"] !== undefined ? { tool_use_id: flags["tool-use-id"] } : {},
6624
+ ...flags["msg-index"] !== undefined ? { msg_index: flags["msg-index"] } : {},
6625
+ ...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
6626
+ });
6627
+ const reason = params.reason?.trim() ?? "";
6628
+ const post = (path, body, retries) => callDataAPI(path, { method: "POST", body: JSON.stringify(body), headers: { "Content-Type": "application/json" } }, retries === undefined ? undefined : { retries });
6629
+ let data;
6630
+ switch (params.kind) {
6631
+ case "problem":
6632
+ data = await post(`/problems/${params.target}/feedback`, { action: "dismiss", reason, actor: "moda-cli" }, 0);
6633
+ break;
6634
+ case "problem-detection":
6635
+ data = await post(`/problems/${params.target}/detection-reviews`, {
6636
+ conversation_id: params.conversation_id,
6637
+ attribution_id: params.attribution_id,
6638
+ verdict: params.undo ? "clear" : "false_positive",
6639
+ ...params.undo ? {} : { note: reason },
6640
+ request_nonce: randomUUID2()
6641
+ });
6642
+ break;
6643
+ case "custom-signal":
6644
+ if (params.undo) {
6645
+ data = await callDataAPI(`/custom-signals/${encodeURIComponent(params.target)}/labels/${encodeURIComponent(params.conversation_id ?? "")}`, { method: "DELETE" }, { retries: 0 });
6646
+ break;
6647
+ }
6648
+ data = await post(`/custom-signals/${encodeURIComponent(params.target)}/labels`, {
6649
+ conversation_id: params.conversation_id,
6650
+ label: "does_not_fit",
6651
+ note: reason,
6652
+ ...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
6653
+ ...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
6654
+ }, 0);
6655
+ break;
6656
+ default: {
6657
+ const apiKind = { quote: "frustration_quote", tool: "tool_call", laziness: "laziness", hallucination: "hallucination" }[params.kind];
6658
+ data = await post("/false-positives", {
6659
+ kind: apiKind,
6660
+ verdict: params.undo ? "clear" : "false_positive",
6661
+ ...params.undo ? {} : { note: reason },
6662
+ conversation_id: params.target,
6663
+ ...params.kind === "tool" ? {
6664
+ tool_name: params.tool,
6665
+ ...params.tool_use_id ? { tool_use_id: params.tool_use_id } : { msg_index: params.msg_index }
6666
+ } : {},
6667
+ ...params.kind === "quote" ? { family: params.family, turn: params.turn, quote: params.quote } : {},
6668
+ request_nonce: randomUUID2()
6669
+ });
6670
+ }
6671
+ }
6672
+ context.output.writeData(data);
6673
+ break;
6674
+ }
6675
+ case "false-positives": {
6676
+ if (!positional) {
6677
+ const kinds = { quote: "frustration_quote", tool: "tool_call", laziness: "laziness", hallucination: "hallucination", "custom-signal": "custom_signal" };
6678
+ const query = new URLSearchParams;
6679
+ if (flags.kind !== undefined) {
6680
+ if (!kinds[flags.kind])
6681
+ throw new CliInputError(`--kind must be one of ${Object.keys(kinds).join("|")}`);
6682
+ query.set("kind", kinds[flags.kind]);
6683
+ }
6684
+ if (flags.limit !== undefined) {
6685
+ if (!/^\d+$/.test(flags.limit) || Number(flags.limit) < 1 || Number(flags.limit) > 200)
6686
+ throw new CliInputError("--limit must be 1-200");
6687
+ query.set("limit", flags.limit);
6688
+ }
6689
+ if (flags.all !== undefined && flags.all !== "false")
6690
+ query.set("include_cleared", "true");
6691
+ const queryString = query.toString() ? `?${query.toString()}` : "";
6692
+ const data2 = await callDataAPI(`/false-positives${queryString}`);
6693
+ context.output.writeData(data2);
6694
+ break;
6695
+ }
6696
+ const data = await callDataAPI(`/conversations/${encodeURIComponent(positional)}/false-positives`);
6697
+ context.output.writeData(data);
6698
+ break;
6699
+ }
6541
6700
  case "emotions": {
6542
6701
  const args = flagsToArgs(flags);
6543
6702
  const params = EmotionsSchema.parse(args);
@@ -6555,6 +6714,28 @@ async function runCommand(command, positional, flags, positionals = positional ?
6555
6714
  context.output.writeData(data);
6556
6715
  break;
6557
6716
  }
6717
+ case "laziness": {
6718
+ const args = flagsToArgs(flags);
6719
+ if (flags["conversation-id"] !== undefined) {
6720
+ args.conversation_id = flags["conversation-id"];
6721
+ }
6722
+ const params = LazinessSchema.parse(args);
6723
+ const query = new URLSearchParams;
6724
+ if (params.days_back)
6725
+ query.set("days_back", params.days_back.toString());
6726
+ if (params.limit)
6727
+ query.set("limit", params.limit.toString());
6728
+ if (params.offset !== undefined)
6729
+ query.set("offset", params.offset.toString());
6730
+ if (params.conversation_id)
6731
+ query.set("conversation_id", params.conversation_id);
6732
+ if (params.family)
6733
+ query.set("family", params.family);
6734
+ const queryString = query.toString() ? `?${query.toString()}` : "";
6735
+ const data = await callDataAPI(`/laziness${queryString}`);
6736
+ context.output.writeData(data);
6737
+ break;
6738
+ }
6558
6739
  case "hallucinations": {
6559
6740
  const args = flagsToArgs(flags);
6560
6741
  if (flags["conversation-id"] !== undefined) {
@@ -7181,6 +7362,32 @@ var commandRegistry = createCommandRegistry([
7181
7362
  ...dataApiDefaults,
7182
7363
  mutability: "write"
7183
7364
  }),
7365
+ legacyCommand({
7366
+ name: "false-positive",
7367
+ description: "Flag a detection as a false positive with a reason: problem, problem-detection, quote, tool, laziness, hallucination, or custom-signal",
7368
+ examples: [
7369
+ 'moda false-positive problem <problem_id> --reason="two unrelated tools"',
7370
+ 'moda false-positive problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --reason="refund was approved"',
7371
+ 'moda false-positive quote <conversation_id> --family=frustration --turn=3 --quote="this is useless lol" --reason="joking"',
7372
+ 'moda false-positive tool <conversation_id> --tool=send_invoice --tool-use-id=<id> --reason="retried upstream"',
7373
+ 'moda false-positive laziness <conversation_id> --reason="reload after context reset is expected"',
7374
+ "moda false-positive hallucination <conversation_id> --undo",
7375
+ "moda false-positive custom-signal <signal_id> --conversation-id=<id> --undo",
7376
+ 'moda false-positive custom-signal <signal_id> --conversation-id=<id> --reason="refund was approved"'
7377
+ ],
7378
+ ...dataApiDefaults,
7379
+ mutability: "write"
7380
+ }),
7381
+ legacyCommand({
7382
+ name: "false-positives",
7383
+ description: "List current false-positive marks across the workspace, or on one trace (with problem detection reviews)",
7384
+ examples: [
7385
+ "moda false-positives",
7386
+ "moda false-positives --kind=tool --all",
7387
+ "moda false-positives <conversation_id>"
7388
+ ],
7389
+ ...dataApiDefaults
7390
+ }),
7184
7391
  legacyCommand({
7185
7392
  name: "signals",
7186
7393
  description: "List custom signals with criteria, status, and 7-day judged/matched stats",
@@ -7221,6 +7428,15 @@ var commandRegistry = createCommandRegistry([
7221
7428
  ],
7222
7429
  ...dataApiDefaults
7223
7430
  }),
7431
+ legacyCommand({
7432
+ name: "laziness",
7433
+ description: "Laziness detections per trace: repeated tool calls, premature handoffs, and other lazy patterns",
7434
+ examples: [
7435
+ "moda laziness --days-back=30",
7436
+ "moda laziness --conversation-id=<id>"
7437
+ ],
7438
+ ...dataApiDefaults
7439
+ }),
7224
7440
  legacyCommand({
7225
7441
  name: "hallucinations",
7226
7442
  description: "Grounding detections: contradicted/verified outputs with rule breakdown",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-09-26T21:50:32.589Z",
4
- "cli_version": "1.35.0",
3
+ "bundled_at": "2026-09-27T08:58:32.181Z",
4
+ "cli_version": "1.36.0",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: moda-cli
3
- version: 2.6.0
3
+ version: 2.7.0
4
4
  description: Query Moda's AI agent trace analytics and manage code-first prompt versions from the terminal — semantic/keyword/hybrid message search, overview KPIs, topic clusters, message context, user frustration detections, tool failures, custom signals, false-positive review of detections, and moda prompts status/sync/promote. Use when the user asks about moda, modaflows, trace or conversation analytics, prompt management, user frustrations, agent observability, tool failure debugging, custom signals, marking a detection as a false positive, wants to find traces or tool calls about a topic, or wants to investigate how their AI agent is performing.
5
5
  ---
6
6
 
@@ -750,6 +750,45 @@ Rules that keep the proof honest:
750
750
 
751
751
  ### 9. Custom signals and false positives
752
752
 
753
+ **Mark anything as a false positive with one command.** `--reason` (why the
754
+ detection is wrong) is optional but recommended; marks are logged for Moda's review.
755
+ A marked item disappears from every list and count (`moda problems`,
756
+ `hallucinations`, `laziness`, `tool-failures`, problem evidence, the dashboard);
757
+ `--undo` brings it back.
758
+
759
+ ```bash
760
+ moda false-positive problem <problem_id> --reason="two unrelated tools" # dismiss: hidden at once
761
+ moda false-positive problem-detection <problem_id> --conversation-id=<id> \
762
+ --attribution-id=<uuid> --reason="refund was approved" # one piece of evidence
763
+ moda false-positive quote <conversation_id> --family=frustration --turn=3 \
764
+ --quote="this is useless lol" --reason="user was joking" # one user quote
765
+ moda false-positive tool <conversation_id> --tool=send_invoice \
766
+ --tool-use-id=<tool_use_id> --reason="retried upstream" # one failing tool call
767
+ moda false-positive laziness <conversation_id> --reason="reload is expected"
768
+ moda false-positive hallucination <conversation_id> --reason="price came from the tool result"
769
+ moda false-positive custom-signal <signal_id> --conversation-id=<id> --reason="refund was approved"
770
+ moda false-positive laziness <conversation_id> --undo # remove a mark (any kind but problem)
771
+ moda false-positives # every current mark (--kind=tool, --all, --limit)
772
+ moda false-positives <conversation_id> # marks + problem reviews on a trace
773
+ ```
774
+
775
+ What each flag does downstream:
776
+
777
+ | Kind | Effect |
778
+ |---|---|
779
+ | `problem` | Dismisses the problem: it leaves `moda problems` and the dashboard at once, and the reason is folded into the next reconcile. Cannot be undone from the CLI |
780
+ | `problem-detection` | Drops that evidence from the problem and teaches the problem reader (same as `detection-review --verdict=false_positive`) |
781
+ | `custom-signal` | Saved as a `does_not_fit` label with the reason, so the signal learns from it and the live match is retracted (same as `signal-label`). `--undo` removes the label |
782
+ | `quote`, `tool`, `laziness`, `hallucination` | Audit only: recorded for review, never fed back to the detectors |
783
+
784
+ Find targets with the read commands: `--family`, `--turn` and `--quote` verbatim
785
+ from a `user_quotes` entry in `moda emotions`; a failing call's `tool_use_id` (or `msg_index` when it has none) from
786
+ `moda tool-failure-detail <tool_name>`;
787
+ laziness traces from `moda laziness`; hallucination traces from
788
+ `moda hallucinations`; custom-signal hits from `moda trace-signals` or
789
+ `moda signal <id> --detections`; problem evidence ids from
790
+ `moda problem <id> --evidence`.
791
+
753
792
  **Custom signals** are tenant-defined behaviors (pass/fail criteria) judged on
754
793
  every trace. Read them, then label hits so the signal learns:
755
794
 
@@ -768,7 +807,7 @@ match; `--label=fits` confirms it. Pass the hit's `anchor_msg_index` (or
768
807
  message, and always give a `--note` when rejecting. Labels are throttled to 10
769
808
  per minute per API key and are never retried automatically.
770
809
 
771
- **Problem detections** (the dashboard's thumbs on a trace) are reviewed with
810
+ **Problem detections** (the dashboard's flag on problem evidence) are reviewed with
772
811
  `detection-review`. A `false_positive` drops the detection out of
773
812
  `moda problem <id> --evidence` and is fed back to the problem reader; `clear`
774
813
  undoes an earlier review.
@@ -954,7 +993,10 @@ npx: `npx -p @moda-ai/cli moda <command>`.
954
993
  | `moda tool-failure-detail <tool_name>` | Per-tool failure breakdown + examples |
955
994
  | `moda problems` | Rank cross-signal Problems by root cause (what to fix first) |
956
995
  | `moda problem <problem_id>` | One Problem: dossier, or `--evidence`/`--reports`/`--traces`/`--feedback` pages (`--conversations` is a legacy alias of `--traces`) (`--limit` 1–50, `--cursor` verbatim keyset token) |
957
- | `moda problem-feedback <problem_id>` | Write: `--action=mark_fixed\|dismiss\|flag_attribution\|rename`. `--reason` required for dismiss/flag_attribution; `--new-name` for rename; `--attribution-id` (UUID) required for flag_attribution |
996
+ | `moda problem-feedback <problem_id>` | Write: `--action=mark_fixed\|dismiss\|flag_attribution\|rename`. `--reason` required for flag_attribution, optional for dismiss; `--new-name` for rename; `--attribution-id` (UUID) required for flag_attribution |
997
+ | `moda false-positive <kind> <target>` | Write: kinds `problem\|problem-detection\|quote\|tool\|laziness\|hallucination\|custom-signal`; `--reason` optional; problem-detection needs `--conversation-id` + `--attribution-id`; quote needs `--family`, `--turn`, `--quote`; tool needs `--tool` plus `--tool-use-id` or `--msg-index`; custom-signal needs `--conversation-id`; `--undo` removes a mark (not for problem) |
998
+ | `moda false-positives [conversation_id]` | Current false-positive marks: all (`--kind`, `--all` to include removed, `--limit`) or one trace with its problem detection reviews |
999
+ | `moda laziness` | Laziness detections per trace (`--days-back`, `--limit` 1-50, `--conversation-id`, `--family`) |
958
1000
  | `moda detection-reviews <conversation_id>` | Problem detections on a trace with review state (`--attribution-id=UUID` narrows to one) |
959
1001
  | `moda detection-review <problem_id>` | Write: `--conversation-id`, `--attribution-id` (UUID), `--verdict=correct\|false_positive\|clear`, optional `--note` |
960
1002
  | `moda signals` | Custom signals: criteria, status, 7-day judged/matched stats |