@moda-ai/cli 1.55.25 → 1.55.26

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 (32) hide show
  1. package/dist/{agent-t7dxmpdq.js → agent-nqmha8ck.js} +8 -8
  2. package/dist/{cli-9hc5bg2r.js → cli-1mbb0ct0.js} +1 -1
  3. package/dist/{cli-mebf5vtr.js → cli-5g1tv80h.js} +5 -5
  4. package/dist/{cli-m0045npe.js → cli-8tfk8qeb.js} +7 -6
  5. package/dist/{cli-m805msp6.js → cli-b4740tnv.js} +7 -7
  6. package/dist/{cli-qvg26twp.js → cli-cdrvjva5.js} +11 -8
  7. package/dist/{cli-7xc585hc.js → cli-d577kq6h.js} +5 -5
  8. package/dist/{cli-dn91fb56.js → cli-jgfm7w1r.js} +89 -47
  9. package/dist/{cli-9btx12wp.js → cli-jtyvbd8d.js} +1 -1
  10. package/dist/{cli-qycsmxev.js → cli-k6q8dbpk.js} +3 -3
  11. package/dist/{cli-0wr3483h.js → cli-q6f6vrrr.js} +1 -1
  12. package/dist/{cli-dve50x7p.js → cli-sqsjd08w.js} +784 -725
  13. package/dist/cli.js +708 -232
  14. package/dist/{doctor-q21mr5ya.js → doctor-w913fj4g.js} +8 -8
  15. package/dist/{fix-0qdtdz5h.js → fix-crkwdkt4.js} +6 -6
  16. package/dist/{harness-github-actions-atc5bbkj.js → harness-github-actions-fjhvhgpj.js} +5 -5
  17. package/dist/{harness-vzy7n7ea.js → harness-x3e7d68b.js} +5 -5
  18. package/dist/{index-4b1ybz51.js → index-2kf3fnnb.js} +11 -11
  19. package/dist/{production-intelligence-c8e0xw3n.js → production-intelligence-7n83c701.js} +75 -15
  20. package/dist/{profile-commands-6e2dgv72.js → profile-commands-ct2t0bfr.js} +3 -3
  21. package/dist/prompt-source-mcp.js +5 -5
  22. package/dist/{prompts-3h5jcjrw.js → prompts-92aqqb2k.js} +6 -6
  23. package/dist/{provision-q86phh4a.js → provision-gn8raqpt.js} +5 -5
  24. package/dist/{registry-s2vdsfw8.js → registry-ex162j5y.js} +3 -3
  25. package/dist/{skills-vq3njc8a.js → skills-2mbsj9bt.js} +5 -5
  26. package/dist/{sync-hq9dpqpr.js → sync-kghmmmk5.js} +7 -7
  27. package/package.json +1 -1
  28. package/skills/integration/index.json +2 -2
  29. package/skills/moda-cli/SKILL.md +4 -1
  30. package/skills/moda-cli/references/dashboard-parity.md +1 -1
  31. package/skills/moda-cli/references/output-protocol.md +35 -10
  32. package/skills/moda-cli/references/search-and-traces.md +20 -2
@@ -689,767 +689,767 @@ function resetApiRequestCount() {
689
689
  apiRequestCount = 0;
690
690
  }
691
691
 
692
- // src/batch-reads.ts
693
- import { readFile } from "node:fs/promises";
694
- var BATCH_MAX_TARGETS = 25;
695
- var BATCH_CONCURRENCY = 4;
696
- var ANCHOR_SUFFIX = /^(.+)@(\d+)$/;
697
- function parseBatchTarget(text, anchors, usage) {
698
- const raw = text.trim();
699
- if (raw.startsWith("{")) {
700
- let parsed;
701
- try {
702
- parsed = JSON.parse(raw);
703
- } catch {
704
- throw new CliInputError(`Could not parse the JSON target ${JSON.stringify(raw)}.`, usage, "bad_batch_target");
705
- }
706
- const record = parsed;
707
- const id = record?.id ?? record?.conversation_id ?? record?.trace_id;
708
- if (typeof id !== "string" || id.trim() === "") {
709
- throw new CliInputError(`JSON target ${JSON.stringify(raw)} has no "id" string.`, usage, "bad_batch_target");
710
- }
711
- const index = record.msg_index ?? record.message_index;
712
- if (index !== undefined && index !== null) {
713
- if (!anchors) {
714
- throw new CliInputError(`JSON target ${JSON.stringify(raw)} has a msg_index, which this command does not take.`, usage, "bad_batch_target");
715
- }
716
- if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) {
717
- throw new CliInputError(`JSON target ${JSON.stringify(raw)} has an invalid msg_index (want an integer >= 0).`, usage, "bad_batch_target");
718
- }
719
- return { raw, id, msgIndex: index };
720
- }
721
- return { raw, id };
722
- }
723
- if (anchors) {
724
- const match = ANCHOR_SUFFIX.exec(raw);
725
- if (match) {
726
- const msgIndex = Number(match[2]);
727
- if (!Number.isSafeInteger(msgIndex)) {
728
- throw new CliInputError(`Target ${JSON.stringify(raw)} has an @msg_index past ${Number.MAX_SAFE_INTEGER}. ` + 'If the id itself ends in @<digits>, pass it as {"id": "..."}.', usage, "bad_batch_target");
729
- }
730
- return { raw, id: match[1], msgIndex };
731
- }
732
- }
733
- return { raw, id: raw };
692
+ // ../../packages/harness-snapshot/src/policy.ts
693
+ var MAX_SCANNED_FILE_BYTES = 512 * 1024;
694
+ var DEFAULT_MAX_INVENTORY_FILES = 12000;
695
+ var DEFAULT_MAX_INVENTORY_TEXT_BYTES = 48 * 1024 * 1024;
696
+ var MAX_SNAPSHOT_BODY_BYTES = 4500000;
697
+ var MAX_RECORDED_SKIPS = 1000;
698
+ var SKIPPED_DIRS = new Set([
699
+ ".angular",
700
+ ".cache",
701
+ ".expo",
702
+ ".git",
703
+ ".hg",
704
+ ".idea",
705
+ ".svn",
706
+ ".next",
707
+ ".nuxt",
708
+ ".output",
709
+ ".parcel-cache",
710
+ ".pytest_cache",
711
+ ".ruff_cache",
712
+ ".serverless",
713
+ ".sst",
714
+ ".turbo",
715
+ ".wrangler",
716
+ ".wrangler-shared",
717
+ ".moda",
718
+ ".moda-test",
719
+ ".moda-test-home",
720
+ ".moda-cli-workspaces",
721
+ ".moda-cli-benchmarks",
722
+ "node_modules",
723
+ "dist",
724
+ "build",
725
+ "coverage",
726
+ "out",
727
+ "target",
728
+ "logs",
729
+ "tmp",
730
+ "temp",
731
+ "__pycache__",
732
+ ".venv",
733
+ "venv"
734
+ ]);
735
+ var SKIPPED_PATH_PREFIXES = [
736
+ ".claude/worktrees",
737
+ ".codex/environments"
738
+ ];
739
+ function posixNormalize(path) {
740
+ const absolute = path.startsWith("/");
741
+ const parts = path.split("/").filter((part) => part !== "" && part !== ".");
742
+ const normalized = parts.join("/");
743
+ return absolute ? `/${normalized}` : normalized;
734
744
  }
735
- function parseTargetLines(text, anchors, usage) {
736
- return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "").map((line) => parseBatchTarget(line, anchors, usage));
745
+ function basename(path) {
746
+ const normalized = posixNormalize(path);
747
+ const index = normalized.lastIndexOf("/");
748
+ return index === -1 ? normalized : normalized.slice(index + 1);
737
749
  }
738
- async function readStdinText() {
739
- if (process.stdin.isTTY) {
740
- throw new CliInputError("--ids-from=- reads targets from stdin, but stdin is a terminal. Pipe ids in (one per line) or pass a file path.", undefined, "ids_from_tty");
741
- }
742
- const chunks = [];
743
- for await (const chunk of process.stdin)
744
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
745
- return Buffer.concat(chunks).toString("utf8");
750
+ function extname(path) {
751
+ const name = basename(path);
752
+ const index = name.lastIndexOf(".");
753
+ return index <= 0 ? "" : name.slice(index);
746
754
  }
747
- async function readIdsFrom(source, usage) {
748
- if (source === "" || source === "true") {
749
- throw new CliInputError("--ids-from needs a value: - (stdin) or a file path.", usage, "missing_flag_value");
750
- }
751
- if (source === "-")
752
- return readStdinText();
753
- try {
754
- return await readFile(source, "utf8");
755
- } catch (error) {
756
- const code = error.code;
757
- throw new CliInputError(`Could not read --ids-from file ${JSON.stringify(source)}${typeof code === "string" ? ` (${code})` : ""}.`, usage, "ids_from_unreadable");
758
- }
755
+ function isSkippedDir(path) {
756
+ return SKIPPED_DIRS.has(basename(path));
759
757
  }
760
- async function collectBatchTargets(positionals, idsFrom, options) {
761
- const targets = positionals.map((value) => parseBatchTarget(value, options.anchors, options.usage));
762
- if (idsFrom !== undefined) {
763
- targets.push(...parseTargetLines(await readIdsFrom(idsFrom, options.usage), options.anchors, options.usage));
764
- }
765
- const seen = new Set;
766
- const unique = targets.filter((target) => {
767
- const key = `${target.id}\x00${target.msgIndex ?? ""}`;
768
- if (seen.has(key))
769
- return false;
770
- seen.add(key);
771
- return true;
772
- });
773
- if (unique.length === 0) {
774
- throw new CliInputError(`No targets: --ids-from gave no ids (one per line).`, options.usage, "missing_positional");
775
- }
776
- if (unique.length > BATCH_MAX_TARGETS) {
777
- throw new CliInputError(`Got ${unique.length} targets; 'moda ${options.command}' reads at most ${BATCH_MAX_TARGETS} per call, so nothing was read. ` + `Split the ids into calls of ${BATCH_MAX_TARGETS} or fewer.`, options.usage, "batch_too_large");
778
- }
779
- return unique;
758
+ function isSkippedPathPrefix(path) {
759
+ const normalized = posixNormalize(path);
760
+ return SKIPPED_PATH_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
780
761
  }
781
- async function mapWithConcurrency(items, limit, fn) {
782
- const results = new Array(items.length);
783
- let next = 0;
784
- let failure;
785
- const worker = async () => {
786
- while (!failure && next < items.length) {
787
- const index = next++;
788
- try {
789
- results[index] = await fn(items[index], index);
790
- } catch (error) {
791
- failure ??= { error };
792
- }
793
- }
794
- };
795
- await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
796
- if (failure)
797
- throw failure.error;
798
- return results;
762
+ function isSourceFile(path) {
763
+ return /\.(ts|tsx|js|jsx|mjs|cjs|py)$/i.test(path);
799
764
  }
800
- function idNeedsJsonTarget(id) {
801
- return ANCHOR_SUFFIX.test(id);
765
+ function isPackageOrLockfile(path) {
766
+ return path === "package.json" || path === "pyproject.toml" || path === "bun.lock" || path === "bun.lockb" || path === "pnpm-lock.yaml" || path === "yarn.lock";
802
767
  }
803
- function contextTarget(id) {
804
- const verbatim = id === id.trim() && !id.startsWith("{") && !idNeedsJsonTarget(id);
805
- return verbatim ? id : JSON.stringify({ id });
768
+ function isPromptLikePath(path) {
769
+ return /(^|\/)prompts?\//i.test(path) && /\.(prompt\.)?(md|json|ya?ml)$/i.test(path);
806
770
  }
807
-
808
- // src/transforms.ts
809
- function asRecord(value) {
810
- if (value && typeof value === "object" && !Array.isArray(value)) {
811
- return value;
812
- }
813
- return null;
771
+ function isSkillLikePath(path) {
772
+ return /(^|\/)(\.claude\/skills|\.cursor\/rules|skills?)\//i.test(path) && /\.(md|mdc)$/i.test(path);
814
773
  }
815
- function asString(value) {
816
- return typeof value === "string" ? value : null;
774
+ function isEvalLikePath(path) {
775
+ return /(^|\/)(evals?|tests?|__tests__)\//i.test(path) && /\.(ts|tsx|js|mjs|py|json|ya?ml)$/i.test(path);
817
776
  }
818
- function asNumber(value) {
819
- return typeof value === "number" && Number.isFinite(value) ? value : null;
777
+ function isDeploymentLikePath(path) {
778
+ return /(^|\/)(wrangler\.toml|vercel\.json|render\.ya?ml|railway\.json|Dockerfile|docker-compose\.ya?ml)$/i.test(path) || /^\.github\/workflows\/[^/]+\.ya?ml$/i.test(path);
820
779
  }
821
- function frustrationAnchorIndex(row) {
822
- if (!row)
823
- return null;
824
- const userQuotes = Array.isArray(row.user_quotes) ? row.user_quotes : [];
825
- const firstQuote = asRecord(userQuotes[0]);
826
- const resolved = asNumber(firstQuote?.msg_index) ?? asNumber(row.anchor_msg_index);
827
- if (resolved !== null)
828
- return resolved;
829
- const keyTurns = Array.isArray(row.key_turns) ? row.key_turns : [];
830
- const firstTurn = keyTurns.find((t) => typeof t === "number");
831
- if (firstTurn !== undefined)
832
- return firstTurn;
833
- return asNumber(firstQuote?.turn);
780
+ function isDeveloperAgentConfigPath(path) {
781
+ return path === ".claude/settings.json" || path === ".codex/hooks.json" || path === ".cursor/hooks.json";
834
782
  }
835
- function buildFrustrationAnchor(row) {
836
- const conversation_id = asString(row.conversation_id) ?? "";
837
- const userQuotes = Array.isArray(row.user_quotes) ? row.user_quotes : [];
838
- const firstQuote = asRecord(userQuotes[0]);
839
- const keyTurnsRaw = Array.isArray(row.key_turns) ? row.key_turns : [];
840
- const all_turns = keyTurnsRaw.filter((t) => typeof t === "number");
841
- const msg_index = frustrationAnchorIndex(row);
842
- let signal = null;
843
- let quote_preview = null;
844
- if (firstQuote) {
845
- signal = asString(firstQuote.signal);
846
- const quote = asString(firstQuote.quote);
847
- if (quote !== null) {
848
- let end = 0;
849
- let points = 0;
850
- for (const ch of quote) {
851
- if (points++ === 200)
852
- break;
853
- end += ch.length;
854
- }
855
- quote_preview = quote.slice(0, end);
856
- }
857
- }
858
- return {
859
- kind: "frustration",
860
- conversation_id,
861
- msg_index,
862
- signal,
863
- quote_preview,
864
- all_turns,
865
- no_anchor: msg_index === null
866
- };
783
+ function isReadmeOrAgents(path) {
784
+ const name = basename(path);
785
+ return name === "README.md" || name === "AGENTS.md";
867
786
  }
868
- function buildToolFailureAnchor(row, toolName) {
869
- const conversation_id = asString(row.conversation_id) ?? "";
870
- const msg_index = asNumber(row.msg_index);
871
- const tool_use_id = asString(row.tool_use_id);
872
- const error_subtype = asString(row.error_subtype);
873
- return {
874
- kind: "tool_failure",
875
- conversation_id,
876
- msg_index,
877
- tool_name: toolName,
878
- tool_use_id,
879
- error_subtype,
880
- no_anchor: msg_index === null
881
- };
882
- }
883
- function frustrationsFromEmotions(response) {
884
- if (!Array.isArray(response.detections) || Array.isArray(response.frustrations))
885
- return response;
886
- const summary = asRecord(response.summary) ?? {};
887
- const pagination = asRecord(response.pagination) ?? {};
888
- const byFamily = asRecord(summary.by_family) ?? {};
889
- const totalAnalyzed = asNumber(summary.conversations_analyzed) ?? 0;
890
- const byFamilyConversations = asRecord(summary.conversations_by_family);
891
- const frustrated = asNumber(byFamilyConversations?.frustration) ?? asNumber(byFamily.frustration) ?? 0;
892
- const frustrations = response.detections.map((item) => asRecord(item)).filter((row) => row !== null && (row.family === undefined || row.family === "frustration")).map(({ conversation_context, score, is_detected, ...row }) => ({
893
- ...row,
894
- is_frustrated: is_detected === true,
895
- frustration_score: score,
896
- target: row.elicitor === "bot" || row.elicitor === "external" ? row.elicitor : "ambiguous",
897
- conversation: conversation_context ?? null
898
- }));
899
- const atRisk = asNumber(pagination.at_risk) ?? Math.max(0, (asNumber(pagination.total) ?? 0) - frustrated);
900
- const signalBreakdown = {};
901
- for (const item of Array.isArray(response.signal_breakdown) ? response.signal_breakdown : []) {
902
- const row = asRecord(item);
903
- if (row?.family === "frustration" && typeof row.signal === "string") {
904
- signalBreakdown[row.signal] = asNumber(row.count) ?? 0;
905
- }
906
- }
907
- const { detections: _detections, ...rest } = response;
908
- return {
909
- ...rest,
910
- summary: {
911
- total_analyzed: totalAnalyzed,
912
- frustrated_count: frustrated,
913
- at_risk_count: atRisk,
914
- frustration_rate_pct: totalAnalyzed > 0 ? Math.round(frustrated / totalAnalyzed * 1000) / 10 : 0
915
- },
916
- signal_breakdown: signalBreakdown,
917
- frustrations,
918
- source: "emotion_detections"
919
- };
920
- }
921
- function withFrustrationAnchors(response) {
922
- if (!Array.isArray(response.frustrations))
923
- return response;
924
- const frustrations = response.frustrations.map((item) => {
925
- const row = asRecord(item);
926
- if (!row)
927
- return item;
928
- const anchor = buildFrustrationAnchor(row);
929
- return { ...row, anchor };
930
- });
931
- return { ...response, frustrations };
932
- }
933
- var TOOL_FAILURE_CONTEXT_RADIUS = 2;
934
- function shellWord(value) {
935
- return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
936
- }
937
- function trimFailureWindow(value, radius, fallbackCenter) {
938
- const conv = asRecord(value);
939
- if (!conv)
940
- return value ?? null;
941
- const { coverage: _coverage, context, ...convRest } = conv;
942
- const ctx = asRecord(context);
943
- if (!ctx)
944
- return convRest;
945
- const center = asNumber(ctx.center_index) ?? fallbackCenter;
946
- const messages = (Array.isArray(ctx.messages) ? ctx.messages : []).map((message) => asRecord(message)).filter((message) => {
947
- const index = asNumber(message?.index);
948
- return message !== null && (center === null || index === null || Math.abs(index - center) <= radius);
949
- }).map(({ content_blocks, ...message }) => {
950
- const covered = typeof message.content === "string" && message.content.length > 0 || Array.isArray(message.tool_calls) && message.tool_calls.length > 0 || Array.isArray(message.tool_results) && message.tool_results.length > 0;
951
- return covered || content_blocks === undefined ? message : { ...message, content_blocks };
952
- });
953
- const indexes = messages.map((message) => asNumber(message.index)).filter((index) => index !== null);
954
- return {
955
- ...convRest,
956
- context: {
957
- ...ctx,
958
- ...indexes.length > 0 ? { from_index: Math.min(...indexes), to_index: Math.max(...indexes) } : {},
959
- messages
960
- }
961
- };
962
- }
963
- function trimToolFailureConversations(response, opts) {
964
- if (!Array.isArray(response.examples))
965
- return response;
966
- const examples = response.examples.map((item) => {
967
- const row = asRecord(item);
968
- if (!row)
969
- return item;
970
- const { conversation, window, ...rest } = row;
971
- const conversationId = asString(row.conversation_id);
972
- const msgIndex = asNumber(asRecord(row.anchor)?.msg_index) ?? asNumber(row.msg_index);
973
- return {
974
- ...rest,
975
- ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex} --window=${TOOL_FAILURE_CONTEXT_RADIUS}` } : {},
976
- ...opts.drop ? {} : { conversation: trimFailureWindow(conversation, TOOL_FAILURE_CONTEXT_RADIUS, msgIndex) },
977
- ..."window" in row ? { window: trimFailureWindow(window, opts.windowRadius ?? TOOL_FAILURE_CONTEXT_RADIUS, msgIndex) } : {}
978
- };
979
- });
980
- return { ...response, examples };
981
- }
982
- function withToolFailureAnchors(response) {
983
- if (!Array.isArray(response.examples))
984
- return response;
985
- const toolName = asString(response.tool_name) ?? "";
986
- const examples = response.examples.map((item) => {
987
- const row = asRecord(item);
988
- if (!row)
989
- return item;
990
- const anchor = buildToolFailureAnchor(row, toolName);
991
- return { ...row, anchor };
992
- });
993
- return { ...response, examples };
994
- }
995
- function embeddedWindowSize(conversation) {
996
- if (!conversation)
997
- return null;
998
- const ctx = asRecord(conversation.context);
999
- if (!ctx)
1000
- return null;
1001
- const center = asNumber(ctx.center_index);
1002
- const from = asNumber(ctx.from_index);
1003
- const to = asNumber(ctx.to_index);
1004
- if (center === null || from === null || to === null)
1005
- return null;
1006
- return Math.min(center - from, to - center);
1007
- }
1008
- var DETECTION_CONTEXT_RADIUS = 2;
1009
- function withoutDetectionWindows(response, listKey, windowKey) {
1010
- const list = response[listKey];
1011
- if (!Array.isArray(list))
1012
- return response;
1013
- const rows = list.map((item) => {
1014
- const row = asRecord(item);
1015
- if (!row)
1016
- return item;
1017
- const { [windowKey]: _window, ...rest } = row;
1018
- const conversationId = asString(row.conversation_id);
1019
- const msgIndex = asNumber(asRecord(row.anchor)?.msg_index) ?? frustrationAnchorIndex(row);
1020
- return {
1021
- ...rest,
1022
- ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex} --window=${DETECTION_CONTEXT_RADIUS}` } : {}
1023
- };
1024
- });
1025
- return { ...response, [listKey]: rows };
1026
- }
1027
- var PROBLEM_COMPACT_EVIDENCE = 5;
1028
- var PROBLEM_COMPACT_OMITTED_BLOCKS = [
1029
- "rubric",
1030
- "trend",
1031
- "sub_problems",
1032
- "affected_conversations",
1033
- "remainder",
1034
- "confidence",
1035
- "change_history",
1036
- "stories"
1037
- ];
1038
- function readCommand(command, purpose) {
1039
- return { command, purpose, mutability: "read", requires_approval: false };
787
+ function shouldInventoryFile(path) {
788
+ const normalized = posixNormalize(path);
789
+ const name = basename(normalized);
790
+ const extension = extname(normalized).toLowerCase();
791
+ if (isSourceFile(normalized))
792
+ return true;
793
+ if (isPackageOrLockfile(normalized))
794
+ return true;
795
+ if (isPromptLikePath(normalized))
796
+ return true;
797
+ if (isSkillLikePath(normalized))
798
+ return true;
799
+ if (isEvalLikePath(normalized))
800
+ return true;
801
+ if (isDeploymentLikePath(normalized))
802
+ return true;
803
+ if (isDeveloperAgentConfigPath(normalized))
804
+ return true;
805
+ if (isReadmeOrAgents(normalized))
806
+ return true;
807
+ return extension === ".toml" && (name === "pyproject.toml" || name === "wrangler.toml");
1040
808
  }
1041
- function evidenceMsgIndex(row) {
1042
- return asNumber(row.anchor_msg_index) ?? asNumber(asRecord(asRecord(row.snippet_parsed)?.anchor)?.msg_index);
809
+ function isTextFile(path) {
810
+ const extension = extname(path).toLowerCase();
811
+ return [
812
+ "",
813
+ ".cjs",
814
+ ".js",
815
+ ".json",
816
+ ".jsx",
817
+ ".md",
818
+ ".mdc",
819
+ ".mjs",
820
+ ".py",
821
+ ".toml",
822
+ ".ts",
823
+ ".tsx",
824
+ ".txt",
825
+ ".yaml",
826
+ ".yml"
827
+ ].includes(extension);
1043
828
  }
1044
- function compactProblemDossier(response, problemId) {
1045
- const id = shellWord(asString(response.problem_id) ?? problemId);
1046
- const evidence = (Array.isArray(response.evidence) ? response.evidence : []).map((item) => asRecord(item)).filter((row) => row !== null);
1047
- const topEvidence = evidence.slice(0, PROBLEM_COMPACT_EVIDENCE).map((row) => {
1048
- const conversationId = asString(row.conversation_id);
1049
- const msgIndex = evidenceMsgIndex(row);
1050
- return {
1051
- attribution_id: row.attribution_id ?? null,
1052
- conversation_id: conversationId,
1053
- msg_index: msgIndex,
1054
- signal_family: row.signal_family ?? null,
1055
- signal_type: row.signal_type ?? null,
1056
- confidence: row.confidence ?? null,
1057
- causal_rationale: row.causal_rationale ?? null,
1058
- door: row.door ?? null,
1059
- replay_verified: row.replay_verified ?? null,
1060
- ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex}` } : {}
1061
- };
1062
- });
1063
- const reportsBlock = asRecord(response.investigation_reports);
1064
- const reports = (Array.isArray(reportsBlock?.items) ? reportsBlock.items : []).map((item) => asRecord(item)).filter((row) => row !== null).map((row) => ({
1065
- report_id: row.report_id ?? null,
1066
- conversation_id: row.conversation_id ?? null,
1067
- mechanism: row.mechanism ?? null,
1068
- suspected_surface: row.suspected_surface ?? null,
1069
- severity: row.severity ?? null,
1070
- problem_found: row.problem_found ?? null
1071
- }));
1072
- const verification = asRecord(response.verification);
1073
- const { samples: _samples, ...verificationCounts } = verification ?? {};
1074
- const dossier = asRecord(response.dossier);
1075
- const { representative_story_keys: _storyKeys, ...dossierSummary } = dossier ?? {};
1076
- const trend = Array.isArray(response.trend) ? response.trend : [];
1077
- const data = {
1078
- found: response.found ?? true,
1079
- problem_id: response.problem_id ?? problemId,
1080
- requested_problem_id: response.requested_problem_id ?? problemId,
1081
- alias_resolved: response.alias_resolved ?? false,
1082
- view: "compact",
1083
- header: response.header ?? null,
1084
- dossier: dossier ? dossierSummary : null,
1085
- verification: verification ? verificationCounts : null,
1086
- ...response.impact_identity !== undefined ? { impact_identity: response.impact_identity } : {},
1087
- ...response.hierarchy !== undefined ? { hierarchy: response.hierarchy } : {},
1088
- ...trend.length > 0 ? { trend_latest: trend[trend.length - 1], trend_points: trend.length } : {},
1089
- top_evidence: topEvidence,
1090
- evidence_in_dossier: evidence.length,
1091
- reports,
1092
- omitted: {
1093
- evidence_rows: Math.max(0, evidence.length - topEvidence.length),
1094
- report_narratives: reports.length,
1095
- reports_has_more: reportsBlock?.has_more === true,
1096
- blocks: PROBLEM_COMPACT_OMITTED_BLOCKS.filter((key) => (key in response))
1097
- },
1098
- full_command: `moda problems get ${id} --full`
1099
- };
1100
- const nextCommands = [
1101
- readCommand(`moda problems get ${id} --evidence --limit=20`, "Page every evidence row with its anchor (follow next_cursor)."),
1102
- readCommand(`moda problems get ${id} --reports`, "Page investigation reports with narratives and citations."),
1103
- readCommand(`moda problems get ${id} --traces`, "Page affected traces with their latest anchor."),
1104
- readCommand(`moda problems get ${id} --full`, "The complete dossier, every block (large).")
1105
- ];
1106
- const first = topEvidence.find((row) => typeof row.context_command === "string");
1107
- if (first?.context_command) {
1108
- nextCommands.push(readCommand(first.context_command, "Read the trace around the top evidence anchor."));
1109
- }
1110
- return { data, nextCommands };
829
+ function isSecretishPath(path) {
830
+ const name = basename(path).toLowerCase();
831
+ return name === ".env" || name.startsWith(".env.") || name.includes("secret") || name.includes("private-key") || name.endsWith(".pem");
1111
832
  }
1112
833
 
1113
- // ../../packages/harness-snapshot/src/policy.ts
1114
- var MAX_SCANNED_FILE_BYTES = 512 * 1024;
1115
- var DEFAULT_MAX_INVENTORY_FILES = 12000;
1116
- var DEFAULT_MAX_INVENTORY_TEXT_BYTES = 48 * 1024 * 1024;
1117
- var MAX_SNAPSHOT_BODY_BYTES = 4500000;
1118
- var MAX_RECORDED_SKIPS = 1000;
1119
- var SKIPPED_DIRS = new Set([
1120
- ".angular",
1121
- ".cache",
1122
- ".expo",
1123
- ".git",
1124
- ".hg",
1125
- ".idea",
1126
- ".svn",
1127
- ".next",
1128
- ".nuxt",
1129
- ".output",
1130
- ".parcel-cache",
1131
- ".pytest_cache",
1132
- ".ruff_cache",
1133
- ".serverless",
1134
- ".sst",
1135
- ".turbo",
1136
- ".wrangler",
1137
- ".wrangler-shared",
1138
- ".moda",
1139
- ".moda-test",
1140
- ".moda-test-home",
1141
- ".moda-cli-workspaces",
1142
- ".moda-cli-benchmarks",
1143
- "node_modules",
1144
- "dist",
1145
- "build",
1146
- "coverage",
1147
- "out",
1148
- "target",
1149
- "logs",
1150
- "tmp",
1151
- "temp",
1152
- "__pycache__",
1153
- ".venv",
1154
- "venv"
834
+ // ../../packages/harness-snapshot/src/redact.ts
835
+ var REDACTED = "[REDACTED]";
836
+ var MAX_DEPTH = 256;
837
+ var SECRET_KEYS = new Set([
838
+ "api_key",
839
+ "apikey",
840
+ "token",
841
+ "access_token",
842
+ "authorization",
843
+ "secret",
844
+ "password",
845
+ "cli_token"
1155
846
  ]);
1156
- var SKIPPED_PATH_PREFIXES = [
1157
- ".claude/worktrees",
1158
- ".codex/environments"
847
+ var SECRET_PATTERNS = [
848
+ { pattern: /\bmoda_sk_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
849
+ { pattern: /\bmoda_pk_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
850
+ { pattern: /\bmoda_ct_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
851
+ { pattern: /\bBearer\s+([A-Za-z0-9._~+/-]+=*)\b/gi, prefixLength: 8 },
852
+ { pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
853
+ { pattern: /\bsk-ant-api\d{2}-[A-Za-z0-9_-]{20,}\b/g, prefixLength: 12 },
854
+ { pattern: /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}\b/g, prefixLength: 8 },
855
+ { pattern: /\bAKIA[A-Z0-9]{16}\b/g, prefixLength: 8 },
856
+ { pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g, prefixLength: 8 },
857
+ { pattern: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g, prefixLength: 8 },
858
+ { pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, prefixLength: 8 }
1159
859
  ];
1160
- function posixNormalize(path) {
1161
- const absolute = path.startsWith("/");
1162
- const parts = path.split("/").filter((part) => part !== "" && part !== ".");
1163
- const normalized = parts.join("/");
1164
- return absolute ? `/${normalized}` : normalized;
860
+ function redactDeep(value) {
861
+ return redactValue(value, 0, new WeakSet);
1165
862
  }
1166
- function basename(path) {
1167
- const normalized = posixNormalize(path);
1168
- const index = normalized.lastIndexOf("/");
1169
- return index === -1 ? normalized : normalized.slice(index + 1);
863
+ function redactValue(value, depth, seen) {
864
+ if (typeof value === "string")
865
+ return redactString(value);
866
+ if (value === null || typeof value !== "object")
867
+ return value;
868
+ if (depth >= MAX_DEPTH)
869
+ return "[MaxDepth]";
870
+ if (seen.has(value))
871
+ return "[Circular]";
872
+ seen.add(value);
873
+ if (Array.isArray(value)) {
874
+ try {
875
+ return value.map((item) => redactValue(item, depth + 1, seen));
876
+ } finally {
877
+ seen.delete(value);
878
+ }
879
+ }
880
+ const out = {};
881
+ try {
882
+ for (const [key, item] of Object.entries(value)) {
883
+ out[key] = shouldRedactKey(key) ? redactSecretValue(item) : redactValue(item, depth + 1, seen);
884
+ }
885
+ } finally {
886
+ seen.delete(value);
887
+ }
888
+ return out;
1170
889
  }
1171
- function extname(path) {
1172
- const name = basename(path);
1173
- const index = name.lastIndexOf(".");
1174
- return index <= 0 ? "" : name.slice(index);
890
+ function shouldRedactKey(key) {
891
+ return SECRET_KEYS.has(key.replace(/[-\s]/g, "_").toLowerCase());
1175
892
  }
1176
- function isSkippedDir(path) {
1177
- return SKIPPED_DIRS.has(basename(path));
893
+ function redactSecretValue(value) {
894
+ if (typeof value !== "string")
895
+ return REDACTED;
896
+ const redacted = redactString(value);
897
+ if (redacted !== value)
898
+ return redacted;
899
+ if (/^(configured|missing|not_configured|none|present|absent)$/i.test(value))
900
+ return value;
901
+ return REDACTED;
902
+ }
903
+ function redactStringWithCount(input) {
904
+ let text = input;
905
+ let redactionCount = 0;
906
+ for (const { pattern, prefixLength } of SECRET_PATTERNS) {
907
+ text = text.replace(pattern, (match) => {
908
+ redactionCount += 1;
909
+ if (/^Bearer\s+/i.test(match)) {
910
+ const token = match.replace(/^Bearer\s+/i, "");
911
+ return `Bearer ${token.slice(0, prefixLength)}...[REDACTED]`;
912
+ }
913
+ return `${match.slice(0, prefixLength)}...[REDACTED]`;
914
+ });
915
+ }
916
+ return { text, redactionCount };
1178
917
  }
1179
- function isSkippedPathPrefix(path) {
1180
- const normalized = posixNormalize(path);
1181
- return SKIPPED_PATH_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
918
+ function redactString(input) {
919
+ return redactStringWithCount(input).text;
1182
920
  }
1183
- function isSourceFile(path) {
1184
- return /\.(ts|tsx|js|jsx|mjs|cjs|py)$/i.test(path);
921
+ function redactSnapshotText(text) {
922
+ const parts = text.split(/(\r\n|\r|\n)/);
923
+ let redactionCount = 0;
924
+ for (let index = 0;index < parts.length; index += 2) {
925
+ const redacted = redactStringWithCount(parts[index] ?? "");
926
+ parts[index] = redacted.text;
927
+ redactionCount += redacted.redactionCount;
928
+ }
929
+ return { text: parts.join(""), redactionCount };
1185
930
  }
1186
- function isPackageOrLockfile(path) {
1187
- return path === "package.json" || path === "pyproject.toml" || path === "bun.lock" || path === "bun.lockb" || path === "pnpm-lock.yaml" || path === "yarn.lock";
931
+
932
+ // ../../packages/harness-snapshot/src/pack.ts
933
+ function utf8ByteLength(value) {
934
+ let bytes = 0;
935
+ for (let index = 0;index < value.length; index += 1) {
936
+ const code = value.charCodeAt(index);
937
+ if (code <= 127) {
938
+ bytes += 1;
939
+ } else if (code <= 2047) {
940
+ bytes += 2;
941
+ } else if (code >= 55296 && code <= 56319) {
942
+ const next = value.charCodeAt(index + 1);
943
+ if (next >= 56320 && next <= 57343) {
944
+ bytes += 4;
945
+ index += 1;
946
+ } else {
947
+ bytes += 3;
948
+ }
949
+ } else {
950
+ bytes += 3;
951
+ }
952
+ }
953
+ return bytes;
1188
954
  }
1189
- function isPromptLikePath(path) {
1190
- return /(^|\/)prompts?\//i.test(path) && /\.(prompt\.)?(md|json|ya?ml)$/i.test(path);
955
+ function isValidRelativePath(path) {
956
+ if (path.length === 0 || path.startsWith("/") || path.includes("\\"))
957
+ return false;
958
+ return !path.split("/").some((part) => part === "..");
1191
959
  }
1192
- function isSkillLikePath(path) {
1193
- return /(^|\/)(\.claude\/skills|\.cursor\/rules|skills?)\//i.test(path) && /\.(md|mdc)$/i.test(path);
960
+ function packSnapshot(inputs, opts) {
961
+ const maxFiles = opts.maxFiles ?? DEFAULT_MAX_INVENTORY_FILES;
962
+ const maxTextBytes = opts.maxTextBytes ?? DEFAULT_MAX_INVENTORY_TEXT_BYTES;
963
+ const maxSnapshotBytes = opts.maxSnapshotBytes ?? MAX_SNAPSHOT_BODY_BYTES;
964
+ const files = [];
965
+ const skips = [];
966
+ let inventoryTextBytes = 0;
967
+ let serializedBytes = 0;
968
+ let byteCount = 0;
969
+ let truncated = false;
970
+ const recordSkip = (path, reason) => {
971
+ if (skips.length < MAX_RECORDED_SKIPS)
972
+ skips.push({ path, reason });
973
+ };
974
+ const sorted = [...inputs].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
975
+ for (const input of sorted) {
976
+ if (!isValidRelativePath(input.path)) {
977
+ recordSkip(input.path, "invalid relative path");
978
+ continue;
979
+ }
980
+ const path = posixNormalize(input.path);
981
+ if (input.isSymlink) {
982
+ recordSkip(path, "symlink skipped for safety");
983
+ continue;
984
+ }
985
+ if (isSecretishPath(path)) {
986
+ recordSkip(path, "secret-bearing path");
987
+ continue;
988
+ }
989
+ if (!shouldInventoryFile(path)) {
990
+ recordSkip(path, "not an inventory file");
991
+ continue;
992
+ }
993
+ if (!isTextFile(path)) {
994
+ recordSkip(path, "non-text file");
995
+ continue;
996
+ }
997
+ if (input.byteSize > MAX_SCANNED_FILE_BYTES) {
998
+ recordSkip(path, "file too large");
999
+ truncated = true;
1000
+ continue;
1001
+ }
1002
+ if (files.length >= maxFiles || inventoryTextBytes + input.byteSize > maxTextBytes) {
1003
+ recordSkip(path, "inventory budget");
1004
+ truncated = true;
1005
+ continue;
1006
+ }
1007
+ const text = opts.redact === false ? input.text : redactSnapshotText(input.text).text;
1008
+ const file = { path, text };
1009
+ const serializedCost = utf8ByteLength(JSON.stringify(file)) + 1;
1010
+ if (serializedBytes + serializedCost > maxSnapshotBytes) {
1011
+ recordSkip(path, "snapshot budget");
1012
+ truncated = true;
1013
+ break;
1014
+ }
1015
+ files.push(file);
1016
+ inventoryTextBytes += input.byteSize;
1017
+ serializedBytes += serializedCost;
1018
+ byteCount += utf8ByteLength(text);
1019
+ }
1020
+ return {
1021
+ snapshot: {
1022
+ repo: { name: opts.repoName, root: "." },
1023
+ files,
1024
+ truncated,
1025
+ fileCount: files.length,
1026
+ byteCount,
1027
+ ...opts.gitCommit ? { gitCommit: opts.gitCommit } : {}
1028
+ },
1029
+ skips
1030
+ };
1194
1031
  }
1195
- function isEvalLikePath(path) {
1196
- return /(^|\/)(evals?|tests?|__tests__)\//i.test(path) && /\.(ts|tsx|js|mjs|py|json|ya?ml)$/i.test(path);
1032
+ // src/batch-reads.ts
1033
+ import { readFile } from "node:fs/promises";
1034
+ var BATCH_MAX_TARGETS = 25;
1035
+ var BATCH_CONCURRENCY = 4;
1036
+ var ANCHOR_SUFFIX = /^(.+)@(\d+)$/;
1037
+ function parseBatchTarget(text, anchors, usage) {
1038
+ const raw = text.trim();
1039
+ if (raw.startsWith("{")) {
1040
+ let parsed;
1041
+ try {
1042
+ parsed = JSON.parse(raw);
1043
+ } catch {
1044
+ throw new CliInputError(`Could not parse the JSON target ${JSON.stringify(raw)}.`, usage, "bad_batch_target");
1045
+ }
1046
+ const record = parsed;
1047
+ const id = record?.id ?? record?.conversation_id ?? record?.trace_id;
1048
+ if (typeof id !== "string" || id.trim() === "") {
1049
+ throw new CliInputError(`JSON target ${JSON.stringify(raw)} has no "id" string.`, usage, "bad_batch_target");
1050
+ }
1051
+ const index = record.msg_index ?? record.message_index;
1052
+ if (index !== undefined && index !== null) {
1053
+ if (!anchors) {
1054
+ throw new CliInputError(`JSON target ${JSON.stringify(raw)} has a msg_index, which this command does not take.`, usage, "bad_batch_target");
1055
+ }
1056
+ if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) {
1057
+ throw new CliInputError(`JSON target ${JSON.stringify(raw)} has an invalid msg_index (want an integer >= 0).`, usage, "bad_batch_target");
1058
+ }
1059
+ return { raw, id, msgIndex: index };
1060
+ }
1061
+ return { raw, id };
1062
+ }
1063
+ if (anchors) {
1064
+ const match = ANCHOR_SUFFIX.exec(raw);
1065
+ if (match) {
1066
+ const msgIndex = Number(match[2]);
1067
+ if (!Number.isSafeInteger(msgIndex)) {
1068
+ throw new CliInputError(`Target ${JSON.stringify(raw)} has an @msg_index past ${Number.MAX_SAFE_INTEGER}. ` + 'If the id itself ends in @<digits>, pass it as {"id": "..."}.', usage, "bad_batch_target");
1069
+ }
1070
+ return { raw, id: match[1], msgIndex };
1071
+ }
1072
+ }
1073
+ return { raw, id: raw };
1197
1074
  }
1198
- function isDeploymentLikePath(path) {
1199
- return /(^|\/)(wrangler\.toml|vercel\.json|render\.ya?ml|railway\.json|Dockerfile|docker-compose\.ya?ml)$/i.test(path) || /^\.github\/workflows\/[^/]+\.ya?ml$/i.test(path);
1075
+ function parseTargetLines(text, anchors, usage) {
1076
+ return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "").map((line) => parseBatchTarget(line, anchors, usage));
1200
1077
  }
1201
- function isDeveloperAgentConfigPath(path) {
1202
- return path === ".claude/settings.json" || path === ".codex/hooks.json" || path === ".cursor/hooks.json";
1078
+ async function readStdinText() {
1079
+ if (process.stdin.isTTY) {
1080
+ throw new CliInputError("--ids-from=- reads targets from stdin, but stdin is a terminal. Pipe ids in (one per line) or pass a file path.", undefined, "ids_from_tty");
1081
+ }
1082
+ const chunks = [];
1083
+ for await (const chunk of process.stdin)
1084
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1085
+ return Buffer.concat(chunks).toString("utf8");
1203
1086
  }
1204
- function isReadmeOrAgents(path) {
1205
- const name = basename(path);
1206
- return name === "README.md" || name === "AGENTS.md";
1087
+ async function readIdsFrom(source, usage) {
1088
+ if (source === "" || source === "true") {
1089
+ throw new CliInputError("--ids-from needs a value: - (stdin) or a file path.", usage, "missing_flag_value");
1090
+ }
1091
+ if (source === "-")
1092
+ return readStdinText();
1093
+ try {
1094
+ return await readFile(source, "utf8");
1095
+ } catch (error) {
1096
+ const code = error.code;
1097
+ throw new CliInputError(`Could not read --ids-from file ${JSON.stringify(source)}${typeof code === "string" ? ` (${code})` : ""}.`, usage, "ids_from_unreadable");
1098
+ }
1207
1099
  }
1208
- function shouldInventoryFile(path) {
1209
- const normalized = posixNormalize(path);
1210
- const name = basename(normalized);
1211
- const extension = extname(normalized).toLowerCase();
1212
- if (isSourceFile(normalized))
1213
- return true;
1214
- if (isPackageOrLockfile(normalized))
1215
- return true;
1216
- if (isPromptLikePath(normalized))
1217
- return true;
1218
- if (isSkillLikePath(normalized))
1219
- return true;
1220
- if (isEvalLikePath(normalized))
1221
- return true;
1222
- if (isDeploymentLikePath(normalized))
1223
- return true;
1224
- if (isDeveloperAgentConfigPath(normalized))
1225
- return true;
1226
- if (isReadmeOrAgents(normalized))
1100
+ async function collectBatchTargets(positionals, idsFrom, options) {
1101
+ const targets = positionals.map((value) => parseBatchTarget(value, options.anchors, options.usage));
1102
+ if (idsFrom !== undefined) {
1103
+ targets.push(...parseTargetLines(await readIdsFrom(idsFrom, options.usage), options.anchors, options.usage));
1104
+ }
1105
+ const seen = new Set;
1106
+ const unique = targets.filter((target) => {
1107
+ const key = `${target.id}\x00${target.msgIndex ?? ""}`;
1108
+ if (seen.has(key))
1109
+ return false;
1110
+ seen.add(key);
1227
1111
  return true;
1228
- return extension === ".toml" && (name === "pyproject.toml" || name === "wrangler.toml");
1112
+ });
1113
+ if (unique.length === 0) {
1114
+ throw new CliInputError(`No targets: --ids-from gave no ids (one per line).`, options.usage, "missing_positional");
1115
+ }
1116
+ if (unique.length > BATCH_MAX_TARGETS) {
1117
+ throw new CliInputError(`Got ${unique.length} targets; 'moda ${options.command}' reads at most ${BATCH_MAX_TARGETS} per call, so nothing was read. ` + `Split the ids into calls of ${BATCH_MAX_TARGETS} or fewer.`, options.usage, "batch_too_large");
1118
+ }
1119
+ return unique;
1120
+ }
1121
+ async function mapWithConcurrency(items, limit, fn, options = {}) {
1122
+ const results = new Array(items.length);
1123
+ let next = 0;
1124
+ let failure;
1125
+ const worker = async () => {
1126
+ while (!failure && next < items.length && (options.shouldStart?.(next) ?? true)) {
1127
+ const index = next++;
1128
+ try {
1129
+ results[index] = await fn(items[index], index);
1130
+ } catch (error) {
1131
+ failure ??= { error };
1132
+ }
1133
+ }
1134
+ };
1135
+ await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
1136
+ if (failure)
1137
+ throw failure.error;
1138
+ return next < items.length ? results.slice(0, next) : results;
1229
1139
  }
1230
- function isTextFile(path) {
1231
- const extension = extname(path).toLowerCase();
1232
- return [
1233
- "",
1234
- ".cjs",
1235
- ".js",
1236
- ".json",
1237
- ".jsx",
1238
- ".md",
1239
- ".mdc",
1240
- ".mjs",
1241
- ".py",
1242
- ".toml",
1243
- ".ts",
1244
- ".tsx",
1245
- ".txt",
1246
- ".yaml",
1247
- ".yml"
1248
- ].includes(extension);
1140
+ function idNeedsJsonTarget(id) {
1141
+ return ANCHOR_SUFFIX.test(id);
1249
1142
  }
1250
- function isSecretishPath(path) {
1251
- const name = basename(path).toLowerCase();
1252
- return name === ".env" || name.startsWith(".env.") || name.includes("secret") || name.includes("private-key") || name.endsWith(".pem");
1143
+ function contextTarget(id) {
1144
+ const verbatim = id === id.trim() && !id.startsWith("{") && !idNeedsJsonTarget(id);
1145
+ return verbatim ? id : JSON.stringify({ id });
1253
1146
  }
1254
1147
 
1255
- // ../../packages/harness-snapshot/src/redact.ts
1256
- var REDACTED = "[REDACTED]";
1257
- var MAX_DEPTH = 256;
1258
- var SECRET_KEYS = new Set([
1259
- "api_key",
1260
- "apikey",
1261
- "token",
1262
- "access_token",
1263
- "authorization",
1264
- "secret",
1265
- "password",
1266
- "cli_token"
1267
- ]);
1268
- var SECRET_PATTERNS = [
1269
- { pattern: /\bmoda_sk_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
1270
- { pattern: /\bmoda_pk_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
1271
- { pattern: /\bmoda_ct_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
1272
- { pattern: /\bBearer\s+([A-Za-z0-9._~+/-]+=*)\b/gi, prefixLength: 8 },
1273
- { pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
1274
- { pattern: /\bsk-ant-api\d{2}-[A-Za-z0-9_-]{20,}\b/g, prefixLength: 12 },
1275
- { pattern: /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}\b/g, prefixLength: 8 },
1276
- { pattern: /\bAKIA[A-Z0-9]{16}\b/g, prefixLength: 8 },
1277
- { pattern: /\bAIza[A-Za-z0-9_-]{35}\b/g, prefixLength: 8 },
1278
- { pattern: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g, prefixLength: 8 },
1279
- { pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, prefixLength: 8 }
1280
- ];
1281
- function redactDeep(value) {
1282
- return redactValue(value, 0, new WeakSet);
1283
- }
1284
- function redactValue(value, depth, seen) {
1285
- if (typeof value === "string")
1286
- return redactString(value);
1287
- if (value === null || typeof value !== "object")
1148
+ // src/transforms.ts
1149
+ function asRecord(value) {
1150
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1288
1151
  return value;
1289
- if (depth >= MAX_DEPTH)
1290
- return "[MaxDepth]";
1291
- if (seen.has(value))
1292
- return "[Circular]";
1293
- seen.add(value);
1294
- if (Array.isArray(value)) {
1295
- try {
1296
- return value.map((item) => redactValue(item, depth + 1, seen));
1297
- } finally {
1298
- seen.delete(value);
1152
+ }
1153
+ return null;
1154
+ }
1155
+ function asString(value) {
1156
+ return typeof value === "string" ? value : null;
1157
+ }
1158
+ function asNumber(value) {
1159
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1160
+ }
1161
+ function frustrationAnchorIndex(row) {
1162
+ if (!row)
1163
+ return null;
1164
+ const userQuotes = Array.isArray(row.user_quotes) ? row.user_quotes : [];
1165
+ const firstQuote = asRecord(userQuotes[0]);
1166
+ const resolved = asNumber(firstQuote?.msg_index) ?? asNumber(row.anchor_msg_index);
1167
+ if (resolved !== null)
1168
+ return resolved;
1169
+ const keyTurns = Array.isArray(row.key_turns) ? row.key_turns : [];
1170
+ const firstTurn = keyTurns.find((t) => typeof t === "number");
1171
+ if (firstTurn !== undefined)
1172
+ return firstTurn;
1173
+ return asNumber(firstQuote?.turn);
1174
+ }
1175
+ function buildFrustrationAnchor(row) {
1176
+ const conversation_id = asString(row.conversation_id) ?? "";
1177
+ const userQuotes = Array.isArray(row.user_quotes) ? row.user_quotes : [];
1178
+ const firstQuote = asRecord(userQuotes[0]);
1179
+ const keyTurnsRaw = Array.isArray(row.key_turns) ? row.key_turns : [];
1180
+ const all_turns = keyTurnsRaw.filter((t) => typeof t === "number");
1181
+ const msg_index = frustrationAnchorIndex(row);
1182
+ let signal = null;
1183
+ let quote_preview = null;
1184
+ if (firstQuote) {
1185
+ signal = asString(firstQuote.signal);
1186
+ const quote = asString(firstQuote.quote);
1187
+ if (quote !== null) {
1188
+ let end = 0;
1189
+ let points = 0;
1190
+ for (const ch of quote) {
1191
+ if (points++ === 200)
1192
+ break;
1193
+ end += ch.length;
1194
+ }
1195
+ quote_preview = quote.slice(0, end);
1299
1196
  }
1300
1197
  }
1301
- const out = {};
1302
- try {
1303
- for (const [key, item] of Object.entries(value)) {
1304
- out[key] = shouldRedactKey(key) ? redactSecretValue(item) : redactValue(item, depth + 1, seen);
1198
+ return {
1199
+ kind: "frustration",
1200
+ conversation_id,
1201
+ msg_index,
1202
+ signal,
1203
+ quote_preview,
1204
+ all_turns,
1205
+ no_anchor: msg_index === null
1206
+ };
1207
+ }
1208
+ function buildToolFailureAnchor(row, toolName) {
1209
+ const conversation_id = asString(row.conversation_id) ?? "";
1210
+ const msg_index = asNumber(row.msg_index);
1211
+ const tool_use_id = asString(row.tool_use_id);
1212
+ const error_subtype = asString(row.error_subtype);
1213
+ return {
1214
+ kind: "tool_failure",
1215
+ conversation_id,
1216
+ msg_index,
1217
+ tool_name: toolName,
1218
+ tool_use_id,
1219
+ error_subtype,
1220
+ no_anchor: msg_index === null
1221
+ };
1222
+ }
1223
+ function frustrationsFromEmotions(response) {
1224
+ if (!Array.isArray(response.detections) || Array.isArray(response.frustrations))
1225
+ return response;
1226
+ const summary = asRecord(response.summary) ?? {};
1227
+ const pagination = asRecord(response.pagination) ?? {};
1228
+ const byFamily = asRecord(summary.by_family) ?? {};
1229
+ const totalAnalyzed = asNumber(summary.conversations_analyzed) ?? 0;
1230
+ const byFamilyConversations = asRecord(summary.conversations_by_family);
1231
+ const frustrated = asNumber(byFamilyConversations?.frustration) ?? asNumber(byFamily.frustration) ?? 0;
1232
+ const frustrations = response.detections.map((item) => asRecord(item)).filter((row) => row !== null && (row.family === undefined || row.family === "frustration")).map(({ conversation_context, score, is_detected, ...row }) => ({
1233
+ ...row,
1234
+ is_frustrated: is_detected === true,
1235
+ frustration_score: score,
1236
+ target: row.elicitor === "bot" || row.elicitor === "external" ? row.elicitor : "ambiguous",
1237
+ conversation: conversation_context ?? null
1238
+ }));
1239
+ const atRisk = asNumber(pagination.at_risk) ?? Math.max(0, (asNumber(pagination.total) ?? 0) - frustrated);
1240
+ const signalBreakdown = {};
1241
+ for (const item of Array.isArray(response.signal_breakdown) ? response.signal_breakdown : []) {
1242
+ const row = asRecord(item);
1243
+ if (row?.family === "frustration" && typeof row.signal === "string") {
1244
+ signalBreakdown[row.signal] = asNumber(row.count) ?? 0;
1305
1245
  }
1306
- } finally {
1307
- seen.delete(value);
1308
1246
  }
1309
- return out;
1247
+ const { detections: _detections, ...rest } = response;
1248
+ return {
1249
+ ...rest,
1250
+ summary: {
1251
+ total_analyzed: totalAnalyzed,
1252
+ frustrated_count: frustrated,
1253
+ at_risk_count: atRisk,
1254
+ frustration_rate_pct: totalAnalyzed > 0 ? Math.round(frustrated / totalAnalyzed * 1000) / 10 : 0
1255
+ },
1256
+ signal_breakdown: signalBreakdown,
1257
+ frustrations,
1258
+ source: "emotion_detections"
1259
+ };
1310
1260
  }
1311
- function shouldRedactKey(key) {
1312
- return SECRET_KEYS.has(key.replace(/[-\s]/g, "_").toLowerCase());
1261
+ function withFrustrationAnchors(response) {
1262
+ if (!Array.isArray(response.frustrations))
1263
+ return response;
1264
+ const frustrations = response.frustrations.map((item) => {
1265
+ const row = asRecord(item);
1266
+ if (!row)
1267
+ return item;
1268
+ const anchor = buildFrustrationAnchor(row);
1269
+ return { ...row, anchor };
1270
+ });
1271
+ return { ...response, frustrations };
1313
1272
  }
1314
- function redactSecretValue(value) {
1315
- if (typeof value !== "string")
1316
- return REDACTED;
1317
- const redacted = redactString(value);
1318
- if (redacted !== value)
1319
- return redacted;
1320
- if (/^(configured|missing|not_configured|none|present|absent)$/i.test(value))
1321
- return value;
1322
- return REDACTED;
1273
+ var TOOL_FAILURE_CONTEXT_RADIUS = 2;
1274
+ function shellWord(value) {
1275
+ return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
1276
+ }
1277
+ function trimFailureWindow(value, radius, fallbackCenter) {
1278
+ const conv = asRecord(value);
1279
+ if (!conv)
1280
+ return value ?? null;
1281
+ const { coverage: _coverage, context, ...convRest } = conv;
1282
+ const ctx = asRecord(context);
1283
+ if (!ctx)
1284
+ return convRest;
1285
+ const center = asNumber(ctx.center_index) ?? fallbackCenter;
1286
+ const messages = (Array.isArray(ctx.messages) ? ctx.messages : []).map((message) => asRecord(message)).filter((message) => {
1287
+ const index = asNumber(message?.index);
1288
+ return message !== null && (center === null || index === null || Math.abs(index - center) <= radius);
1289
+ }).map(({ content_blocks, ...message }) => {
1290
+ const covered = typeof message.content === "string" && message.content.length > 0 || Array.isArray(message.tool_calls) && message.tool_calls.length > 0 || Array.isArray(message.tool_results) && message.tool_results.length > 0;
1291
+ return covered || content_blocks === undefined ? message : { ...message, content_blocks };
1292
+ });
1293
+ const indexes = messages.map((message) => asNumber(message.index)).filter((index) => index !== null);
1294
+ return {
1295
+ ...convRest,
1296
+ context: {
1297
+ ...ctx,
1298
+ ...indexes.length > 0 ? { from_index: Math.min(...indexes), to_index: Math.max(...indexes) } : {},
1299
+ messages
1300
+ }
1301
+ };
1302
+ }
1303
+ function trimToolFailureConversations(response, opts) {
1304
+ if (!Array.isArray(response.examples))
1305
+ return response;
1306
+ const examples = response.examples.map((item) => {
1307
+ const row = asRecord(item);
1308
+ if (!row)
1309
+ return item;
1310
+ const { conversation, window, ...rest } = row;
1311
+ const conversationId = asString(row.conversation_id);
1312
+ const msgIndex = asNumber(asRecord(row.anchor)?.msg_index) ?? asNumber(row.msg_index);
1313
+ return {
1314
+ ...rest,
1315
+ ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex} --window=${TOOL_FAILURE_CONTEXT_RADIUS}` } : {},
1316
+ ...opts.drop ? {} : { conversation: trimFailureWindow(conversation, TOOL_FAILURE_CONTEXT_RADIUS, msgIndex) },
1317
+ ..."window" in row ? { window: trimFailureWindow(window, opts.windowRadius ?? TOOL_FAILURE_CONTEXT_RADIUS, msgIndex) } : {}
1318
+ };
1319
+ });
1320
+ return { ...response, examples };
1323
1321
  }
1324
- function redactStringWithCount(input) {
1325
- let text = input;
1326
- let redactionCount = 0;
1327
- for (const { pattern, prefixLength } of SECRET_PATTERNS) {
1328
- text = text.replace(pattern, (match) => {
1329
- redactionCount += 1;
1330
- if (/^Bearer\s+/i.test(match)) {
1331
- const token = match.replace(/^Bearer\s+/i, "");
1332
- return `Bearer ${token.slice(0, prefixLength)}...[REDACTED]`;
1333
- }
1334
- return `${match.slice(0, prefixLength)}...[REDACTED]`;
1335
- });
1336
- }
1337
- return { text, redactionCount };
1322
+ function withToolFailureAnchors(response) {
1323
+ if (!Array.isArray(response.examples))
1324
+ return response;
1325
+ const toolName = asString(response.tool_name) ?? "";
1326
+ const examples = response.examples.map((item) => {
1327
+ const row = asRecord(item);
1328
+ if (!row)
1329
+ return item;
1330
+ const anchor = buildToolFailureAnchor(row, toolName);
1331
+ return { ...row, anchor };
1332
+ });
1333
+ return { ...response, examples };
1338
1334
  }
1339
- function redactString(input) {
1340
- return redactStringWithCount(input).text;
1335
+ function embeddedWindowSize(conversation) {
1336
+ if (!conversation)
1337
+ return null;
1338
+ const ctx = asRecord(conversation.context);
1339
+ if (!ctx)
1340
+ return null;
1341
+ const center = asNumber(ctx.center_index);
1342
+ const from = asNumber(ctx.from_index);
1343
+ const to = asNumber(ctx.to_index);
1344
+ if (center === null || from === null || to === null)
1345
+ return null;
1346
+ return Math.min(center - from, to - center);
1341
1347
  }
1342
- function redactSnapshotText(text) {
1343
- const parts = text.split(/(\r\n|\r|\n)/);
1344
- let redactionCount = 0;
1345
- for (let index = 0;index < parts.length; index += 2) {
1346
- const redacted = redactStringWithCount(parts[index] ?? "");
1347
- parts[index] = redacted.text;
1348
- redactionCount += redacted.redactionCount;
1349
- }
1350
- return { text: parts.join(""), redactionCount };
1348
+ var DETECTION_CONTEXT_RADIUS = 2;
1349
+ function withoutDetectionWindows(response, listKey, windowKey) {
1350
+ const list = response[listKey];
1351
+ if (!Array.isArray(list))
1352
+ return response;
1353
+ const rows = list.map((item) => {
1354
+ const row = asRecord(item);
1355
+ if (!row)
1356
+ return item;
1357
+ const { [windowKey]: _window, ...rest } = row;
1358
+ const conversationId = asString(row.conversation_id);
1359
+ const msgIndex = asNumber(asRecord(row.anchor)?.msg_index) ?? frustrationAnchorIndex(row);
1360
+ return {
1361
+ ...rest,
1362
+ ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex} --window=${DETECTION_CONTEXT_RADIUS}` } : {}
1363
+ };
1364
+ });
1365
+ return { ...response, [listKey]: rows };
1351
1366
  }
1352
-
1353
- // ../../packages/harness-snapshot/src/pack.ts
1354
- function utf8ByteLength(value) {
1355
- let bytes = 0;
1356
- for (let index = 0;index < value.length; index += 1) {
1357
- const code = value.charCodeAt(index);
1358
- if (code <= 127) {
1359
- bytes += 1;
1360
- } else if (code <= 2047) {
1361
- bytes += 2;
1362
- } else if (code >= 55296 && code <= 56319) {
1363
- const next = value.charCodeAt(index + 1);
1364
- if (next >= 56320 && next <= 57343) {
1365
- bytes += 4;
1366
- index += 1;
1367
- } else {
1368
- bytes += 3;
1369
- }
1370
- } else {
1371
- bytes += 3;
1372
- }
1373
- }
1374
- return bytes;
1367
+ var PROBLEM_COMPACT_EVIDENCE = 5;
1368
+ var PROBLEM_COMPACT_OMITTED_BLOCKS = [
1369
+ "rubric",
1370
+ "trend",
1371
+ "sub_problems",
1372
+ "affected_conversations",
1373
+ "remainder",
1374
+ "confidence",
1375
+ "change_history",
1376
+ "stories"
1377
+ ];
1378
+ function readCommand(command, purpose) {
1379
+ return { command, purpose, mutability: "read", requires_approval: false };
1375
1380
  }
1376
- function isValidRelativePath(path) {
1377
- if (path.length === 0 || path.startsWith("/") || path.includes("\\"))
1378
- return false;
1379
- return !path.split("/").some((part) => part === "..");
1381
+ function evidenceMsgIndex(row) {
1382
+ return asNumber(row.anchor_msg_index) ?? asNumber(asRecord(asRecord(row.snippet_parsed)?.anchor)?.msg_index);
1380
1383
  }
1381
- function packSnapshot(inputs, opts) {
1382
- const maxFiles = opts.maxFiles ?? DEFAULT_MAX_INVENTORY_FILES;
1383
- const maxTextBytes = opts.maxTextBytes ?? DEFAULT_MAX_INVENTORY_TEXT_BYTES;
1384
- const maxSnapshotBytes = opts.maxSnapshotBytes ?? MAX_SNAPSHOT_BODY_BYTES;
1385
- const files = [];
1386
- const skips = [];
1387
- let inventoryTextBytes = 0;
1388
- let serializedBytes = 0;
1389
- let byteCount = 0;
1390
- let truncated = false;
1391
- const recordSkip = (path, reason) => {
1392
- if (skips.length < MAX_RECORDED_SKIPS)
1393
- skips.push({ path, reason });
1394
- };
1395
- const sorted = [...inputs].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
1396
- for (const input of sorted) {
1397
- if (!isValidRelativePath(input.path)) {
1398
- recordSkip(input.path, "invalid relative path");
1399
- continue;
1400
- }
1401
- const path = posixNormalize(input.path);
1402
- if (input.isSymlink) {
1403
- recordSkip(path, "symlink skipped for safety");
1404
- continue;
1405
- }
1406
- if (isSecretishPath(path)) {
1407
- recordSkip(path, "secret-bearing path");
1408
- continue;
1409
- }
1410
- if (!shouldInventoryFile(path)) {
1411
- recordSkip(path, "not an inventory file");
1412
- continue;
1413
- }
1414
- if (!isTextFile(path)) {
1415
- recordSkip(path, "non-text file");
1416
- continue;
1417
- }
1418
- if (input.byteSize > MAX_SCANNED_FILE_BYTES) {
1419
- recordSkip(path, "file too large");
1420
- truncated = true;
1421
- continue;
1422
- }
1423
- if (files.length >= maxFiles || inventoryTextBytes + input.byteSize > maxTextBytes) {
1424
- recordSkip(path, "inventory budget");
1425
- truncated = true;
1426
- continue;
1427
- }
1428
- const text = opts.redact === false ? input.text : redactSnapshotText(input.text).text;
1429
- const file = { path, text };
1430
- const serializedCost = utf8ByteLength(JSON.stringify(file)) + 1;
1431
- if (serializedBytes + serializedCost > maxSnapshotBytes) {
1432
- recordSkip(path, "snapshot budget");
1433
- truncated = true;
1434
- break;
1435
- }
1436
- files.push(file);
1437
- inventoryTextBytes += input.byteSize;
1438
- serializedBytes += serializedCost;
1439
- byteCount += utf8ByteLength(text);
1440
- }
1441
- return {
1442
- snapshot: {
1443
- repo: { name: opts.repoName, root: "." },
1444
- files,
1445
- truncated,
1446
- fileCount: files.length,
1447
- byteCount,
1448
- ...opts.gitCommit ? { gitCommit: opts.gitCommit } : {}
1384
+ function compactProblemDossier(response, problemId) {
1385
+ const id = shellWord(asString(response.problem_id) ?? problemId);
1386
+ const evidence = (Array.isArray(response.evidence) ? response.evidence : []).map((item) => asRecord(item)).filter((row) => row !== null);
1387
+ const topEvidence = evidence.slice(0, PROBLEM_COMPACT_EVIDENCE).map((row) => {
1388
+ const conversationId = asString(row.conversation_id);
1389
+ const msgIndex = evidenceMsgIndex(row);
1390
+ return {
1391
+ attribution_id: row.attribution_id ?? null,
1392
+ conversation_id: conversationId,
1393
+ msg_index: msgIndex,
1394
+ signal_family: row.signal_family ?? null,
1395
+ signal_type: row.signal_type ?? null,
1396
+ confidence: row.confidence ?? null,
1397
+ causal_rationale: row.causal_rationale ?? null,
1398
+ door: row.door ?? null,
1399
+ replay_verified: row.replay_verified ?? null,
1400
+ ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex}` } : {}
1401
+ };
1402
+ });
1403
+ const reportsBlock = asRecord(response.investigation_reports);
1404
+ const reports = (Array.isArray(reportsBlock?.items) ? reportsBlock.items : []).map((item) => asRecord(item)).filter((row) => row !== null).map((row) => ({
1405
+ report_id: row.report_id ?? null,
1406
+ conversation_id: row.conversation_id ?? null,
1407
+ mechanism: row.mechanism ?? null,
1408
+ suspected_surface: row.suspected_surface ?? null,
1409
+ severity: row.severity ?? null,
1410
+ problem_found: row.problem_found ?? null
1411
+ }));
1412
+ const verification = asRecord(response.verification);
1413
+ const { samples: _samples, ...verificationCounts } = verification ?? {};
1414
+ const dossier = asRecord(response.dossier);
1415
+ const { representative_story_keys: _storyKeys, ...dossierSummary } = dossier ?? {};
1416
+ const trend = Array.isArray(response.trend) ? response.trend : [];
1417
+ const data = {
1418
+ found: response.found ?? true,
1419
+ problem_id: response.problem_id ?? problemId,
1420
+ requested_problem_id: response.requested_problem_id ?? problemId,
1421
+ alias_resolved: response.alias_resolved ?? false,
1422
+ view: "compact",
1423
+ header: response.header ?? null,
1424
+ dossier: dossier ? dossierSummary : null,
1425
+ verification: verification ? verificationCounts : null,
1426
+ ...response.impact_identity !== undefined ? { impact_identity: response.impact_identity } : {},
1427
+ ...response.hierarchy !== undefined ? { hierarchy: response.hierarchy } : {},
1428
+ ...trend.length > 0 ? { trend_latest: trend[trend.length - 1], trend_points: trend.length } : {},
1429
+ top_evidence: topEvidence,
1430
+ evidence_in_dossier: evidence.length,
1431
+ reports,
1432
+ omitted: {
1433
+ evidence_rows: Math.max(0, evidence.length - topEvidence.length),
1434
+ report_narratives: reports.length,
1435
+ reports_has_more: reportsBlock?.has_more === true,
1436
+ blocks: PROBLEM_COMPACT_OMITTED_BLOCKS.filter((key) => (key in response))
1449
1437
  },
1450
- skips
1438
+ full_command: `moda problems get ${id} --full`
1451
1439
  };
1440
+ const nextCommands = [
1441
+ readCommand(`moda problems get ${id} --evidence --limit=20`, "Page every evidence row with its anchor (follow next_cursor)."),
1442
+ readCommand(`moda problems get ${id} --reports`, "Page investigation reports with narratives and citations."),
1443
+ readCommand(`moda problems get ${id} --traces`, "Page affected traces with their latest anchor."),
1444
+ readCommand(`moda problems get ${id} --full`, "The complete dossier, every block (large).")
1445
+ ];
1446
+ const first = topEvidence.find((row) => typeof row.context_command === "string");
1447
+ if (first?.context_command) {
1448
+ nextCommands.push(readCommand(first.context_command, "Read the trace around the top evidence anchor."));
1449
+ }
1450
+ return { data, nextCommands };
1452
1451
  }
1452
+
1453
1453
  // src/legacy-names.ts
1454
1454
  var LEGACY_NAMES = [
1455
1455
  { legacy: "conversations", use: "moda traces list", canonical: { command: "traces", subcommand: "list" }, silent: true },
@@ -1542,7 +1542,9 @@ function quotedArgumentSpans(text) {
1542
1542
  return spans;
1543
1543
  }
1544
1544
  function canonicalInvocation(entry, rest) {
1545
- let words = [...rest];
1545
+ const separator = rest.indexOf("--");
1546
+ const afterSeparator = separator === -1 ? [] : rest.slice(separator);
1547
+ let words = separator === -1 ? [...rest] : rest.slice(0, separator);
1546
1548
  const takeFlag = (pattern) => {
1547
1549
  const index = words.findIndex((word2) => pattern.test(word2));
1548
1550
  if (index === -1)
@@ -1563,7 +1565,7 @@ function canonicalInvocation(entry, rest) {
1563
1565
  takeFlag(/^--detections(=true)?$/);
1564
1566
  break;
1565
1567
  case "false-positive":
1566
- words = words.map((word) => /^--undo(=true)?$/.test(word) ? "--verdict=clear" : word);
1568
+ words = words.filter((word) => !/^--undo=(false|0)$/i.test(word)).map((word) => /^--undo(=true|=1)?$/i.test(word) ? "--verdict=clear" : word);
1567
1569
  break;
1568
1570
  case "signal-label":
1569
1571
  words = words.map((word) => word.replace(/^--label=fits$/, "--verdict=correct").replace(/^--label=does_not_fit$/, "--verdict=false_positive").replace(/^--label=.*$/, "--verdict=correct|false_positive"));
@@ -1585,7 +1587,7 @@ function canonicalInvocation(entry, rest) {
1585
1587
  out.push(token.startsWith("--family=") ? `${token}${familySuffix}` : token);
1586
1588
  }
1587
1589
  }
1588
- return [...out, ...positionals, ...words].join(" ");
1590
+ return [...out, ...positionals, ...words, ...afterSeparator].join(" ");
1589
1591
  }
1590
1592
  function canonicalCommandText(text) {
1591
1593
  if (!text.includes("moda "))
@@ -1684,20 +1686,51 @@ var MODA_COMMAND = /^(?:moda |.*\| moda )/;
1684
1686
  function withProfileFlag(command, flag) {
1685
1687
  return MODA_COMMAND.test(command) && !/(?:^|\s)--profile(?:[=\s]|$)/.test(command) ? `${command} ${flag}` : command;
1686
1688
  }
1689
+ var GENERATED_COMMAND_KEY = /_commands?$/;
1690
+ var GENERATED_COMMAND_LIST_KEY = /(?:_commands|^recommended_actions|^recommendedActions|^setupPlan)$/;
1691
+ var CUSTOMER_CONTENT_KEYS = new Set([
1692
+ "messages",
1693
+ "context",
1694
+ "conversation",
1695
+ "conversation_context",
1696
+ "window",
1697
+ "tool_calls",
1698
+ "tool_results",
1699
+ "content",
1700
+ "content_blocks",
1701
+ "input",
1702
+ "output",
1703
+ "tool_input",
1704
+ "tool_output",
1705
+ "spans",
1706
+ "ingest",
1707
+ "raw_event",
1708
+ "attributes",
1709
+ "payload",
1710
+ "snippet_parsed",
1711
+ "evidence_snippet",
1712
+ "request",
1713
+ "response"
1714
+ ]);
1687
1715
  function withProfileOnCommands(value, profile) {
1688
1716
  const flag = `--profile=${shellWord(profile)}`;
1689
- const visit = (node, key) => {
1690
- const isCommandKey = key !== undefined && /commands?$/.test(key);
1717
+ const visit = (node, key, inCommandList) => {
1718
+ const isCommandKey = key !== undefined && (GENERATED_COMMAND_KEY.test(key) || inCommandList && key === "command");
1691
1719
  if (typeof node === "string")
1692
1720
  return isCommandKey ? withProfileFlag(node, flag) : node;
1693
- if (Array.isArray(node))
1694
- return node.map((item) => visit(item, isCommandKey ? key : undefined));
1721
+ if (Array.isArray(node)) {
1722
+ const list = key !== undefined && GENERATED_COMMAND_LIST_KEY.test(key);
1723
+ return node.map((item) => typeof item === "string" ? visit(item, isCommandKey ? key : undefined, false) : visit(item, undefined, list));
1724
+ }
1695
1725
  const record = asRecord2(node);
1696
1726
  if (!record)
1697
1727
  return node;
1698
- return Object.fromEntries(Object.entries(record).map(([entryKey, entry]) => [entryKey, visit(entry, entryKey)]));
1728
+ return Object.fromEntries(Object.entries(record).map(([entryKey, entry]) => [
1729
+ entryKey,
1730
+ CUSTOMER_CONTENT_KEYS.has(entryKey) ? entry : visit(entry, entryKey, inCommandList)
1731
+ ]));
1699
1732
  };
1700
- return visit(value, undefined);
1733
+ return visit(value, undefined, false);
1701
1734
  }
1702
1735
  var FALLBACK_RUN_ID = randomUUID();
1703
1736
  var fallbackSeq = 0;
@@ -1879,12 +1912,16 @@ function createCliOutput(options) {
1879
1912
  if (options.quiet && !bypassQuiet)
1880
1913
  return;
1881
1914
  const profile = options.invocation?.flags.profile;
1915
+ const agentOutput = options.mode === "agent" || options.mode === "agent-stream";
1882
1916
  if (profile) {
1883
- data = withProfileOnCommands(data, profile);
1917
+ if (agentOutput)
1918
+ data = withProfileOnCommands(data, profile);
1884
1919
  if (writeOptions.nextCommands)
1885
- writeOptions = { ...writeOptions, nextCommands: withProfileOnCommands(writeOptions.nextCommands, profile) };
1920
+ writeOptions = { ...writeOptions, nextCommands: withProfileOnCommands({ next_commands: writeOptions.nextCommands }, profile).next_commands };
1921
+ if (writeOptions.recommendedActions)
1922
+ writeOptions = { ...writeOptions, recommendedActions: withProfileOnCommands({ recommended_actions: writeOptions.recommendedActions }, profile).recommended_actions };
1886
1923
  }
1887
- if (options.mode === "agent" || options.mode === "agent-stream") {
1924
+ if (agentOutput) {
1888
1925
  if (state.terminal)
1889
1926
  return;
1890
1927
  const envelope = writeEnvelope(data, writeOptions);
@@ -1905,7 +1942,8 @@ function createCliOutput(options) {
1905
1942
  }
1906
1943
  return;
1907
1944
  }
1908
- safeWriteStdout(`${JSON.stringify(canonicalSuggestionFields(redactDeep(data)), null, 2)}
1945
+ const indent = options.mode === "human" || (options.pretty ?? Boolean(process.stdout.isTTY)) ? 2 : undefined;
1946
+ safeWriteStdout(`${JSON.stringify(canonicalSuggestionFields(redactDeep(data)), null, indent)}
1909
1947
  `);
1910
1948
  for (const warning of writeOptions.warnings ?? []) {
1911
1949
  safeWriteStderr(`Warning: ${canonicalCommandText(warning)}
@@ -2368,6 +2406,16 @@ function errorToAgentError(error) {
2368
2406
  blocking: true
2369
2407
  };
2370
2408
  }
2409
+ if (apiErrorStatus(error) === 404 && !error.readable) {
2410
+ const suggestions = "suggestions" in error && Array.isArray(error.suggestions) ? error.suggestions.filter((s) => typeof s === "string") : [];
2411
+ return {
2412
+ code: "not_found",
2413
+ message: `Not found (HTTP 404): ${serverErrorMessage(error) ?? "the Moda API has no such resource"}.`,
2414
+ cause: "The id may be mistyped, from another tenant or profile, or deleted.",
2415
+ ...suggestions.length > 0 ? { suggested_commands: suggestions } : {},
2416
+ blocking: true
2417
+ };
2418
+ }
2371
2419
  if (isApiLikeError(error)) {
2372
2420
  const statusCode = "statusCode" in error ? error.statusCode : undefined;
2373
2421
  const typedCode = "code" in error && typeof error.code === "string" ? error.code : undefined;
@@ -2618,7 +2666,10 @@ function continuationCommand(invocation, overrides) {
2618
2666
  const normalize2 = (key) => key.replace(/_/g, "-");
2619
2667
  const overridden = new Set(Object.keys(overrides).map(normalize2));
2620
2668
  const bare = new Set((invocation.bareFlags ?? []).map(normalize2));
2621
- const parts = ["moda", invocation.command, ...invocation.positionals.map((value) => shellWord(value))];
2669
+ const dashAt = invocation.positionals.findIndex((value) => value.startsWith("-"));
2670
+ const leading = dashAt === -1 ? invocation.positionals : invocation.positionals.slice(0, dashAt);
2671
+ const trailing = dashAt === -1 ? [] : invocation.positionals.slice(dashAt);
2672
+ const parts = ["moda", invocation.command, ...leading.map((value) => shellWord(value))];
2622
2673
  for (const [key, value] of Object.entries(invocation.flags)) {
2623
2674
  const flag = normalize2(key);
2624
2675
  if (overridden.has(flag) || CONTINUATION_SECRET_FLAGS.has(flag))
@@ -2627,13 +2678,17 @@ function continuationCommand(invocation, overrides) {
2627
2678
  }
2628
2679
  for (const [key, value] of Object.entries(overrides))
2629
2680
  parts.push(`--${normalize2(key)}=${shellWord(String(value))}`);
2681
+ if (trailing.length > 0)
2682
+ parts.push("--", ...trailing.map((value) => shellWord(value)));
2630
2683
  return canonicalCommandText(parts.join(" "));
2631
2684
  }
2632
2685
  var PAGED_LIST_BY_COMMAND = {
2633
2686
  "tool-failure-detail": "examples",
2634
2687
  emotions: "detections",
2635
2688
  frustrations: "frustrations",
2636
- "problems remainder": "groups"
2689
+ laziness: "conversations",
2690
+ "problems remainder": "groups",
2691
+ "problem remainder": "groups"
2637
2692
  };
2638
2693
  function pagedListFor(invocation) {
2639
2694
  const { command, positionals } = invocation.routedAs ?? invocation;
@@ -2663,7 +2718,7 @@ function rewritePagination(list, isTopLevel, invocation) {
2663
2718
  const kept = array.length;
2664
2719
  const omitted = originalLength - kept;
2665
2720
  const offset = typeof pagination.offset === "number" && Number.isFinite(pagination.offset) ? pagination.offset : undefined;
2666
- const hadCursor = typeof pagination.next_cursor === "string" && pagination.next_cursor.length > 0;
2721
+ const hadCursor = typeof pagination.next_cursor === "string" && pagination.next_cursor.length > 0 || typeof pagination.nextCursor === "string" && pagination.nextCursor.length > 0;
2667
2722
  if (typeof pagination.limit === "number") {
2668
2723
  pagination.requested_limit = pagination.limit;
2669
2724
  pagination.limit = kept;
@@ -2671,6 +2726,10 @@ function rewritePagination(list, isTopLevel, invocation) {
2671
2726
  pagination.returned = kept;
2672
2727
  pagination.omitted_for_byte_limit = omitted;
2673
2728
  pagination.has_more = true;
2729
+ if ("hasMore" in pagination)
2730
+ pagination.hasMore = true;
2731
+ if ("nextCursor" in pagination && typeof pagination.nextCursor === "string" && pagination.nextCursor)
2732
+ pagination.nextCursor = null;
2674
2733
  const pastOffsetCap = offset !== undefined && offset + kept > MAX_PAGE_OFFSET;
2675
2734
  if (offset !== undefined && !pastOffsetCap)
2676
2735
  pagination.next_offset = offset + kept;
@@ -2852,4 +2911,4 @@ function apiKeyHeaders(key, options = {}, url) {
2852
2911
  return headers;
2853
2912
  }
2854
2913
 
2855
- export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, attachInputDetails, inputDetailsOf, CliAccessError, CliInputRequiredError, CliAuthError, CliNotFoundError, CliCancellationError, isNetworkLikeError, isApiLikeError, apiErrorStatus, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders, recordApiRequest, getApiRequestCount, resetApiRequestCount, BATCH_MAX_TARGETS, BATCH_CONCURRENCY, parseBatchTarget, collectBatchTargets, mapWithConcurrency, contextTarget, asRecord, asString, asNumber, frustrationAnchorIndex, frustrationsFromEmotions, withFrustrationAnchors, shellWord, trimToolFailureConversations, withToolFailureAnchors, embeddedWindowSize, withoutDetectionWindows, compactProblemDossier, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, LEGACY_NAMES, canonicalCommandText, setAgentUpdateNotice, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, outputModeFromFlags, resolveOutputMode, createCliOutput, agentEnvelopeFits, errorToAgentError, agentMaxOutputBytes };
2914
+ export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, attachInputDetails, inputDetailsOf, CliAccessError, CliInputRequiredError, CliAuthError, CliNotFoundError, CliCancellationError, isNetworkLikeError, isApiLikeError, apiErrorStatus, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders, recordApiRequest, getApiRequestCount, resetApiRequestCount, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, BATCH_MAX_TARGETS, BATCH_CONCURRENCY, parseBatchTarget, collectBatchTargets, mapWithConcurrency, contextTarget, asRecord, asString, asNumber, frustrationAnchorIndex, frustrationsFromEmotions, withFrustrationAnchors, shellWord, trimToolFailureConversations, withToolFailureAnchors, embeddedWindowSize, withoutDetectionWindows, compactProblemDossier, LEGACY_NAMES, canonicalCommandText, setAgentUpdateNotice, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, outputModeFromFlags, resolveOutputMode, createCliOutput, agentEnvelopeFits, errorToAgentError, agentMaxOutputBytes, continuationCommand };