@moda-ai/cli 1.28.6 → 1.29.1

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.
@@ -124,7 +124,7 @@ function resetApiRequestCount() {
124
124
  }
125
125
 
126
126
  // src/harness-remote.ts
127
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
127
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
128
128
  import { join as join3 } from "node:path";
129
129
 
130
130
  // src/markdown-terminal.ts
@@ -542,7 +542,7 @@ function createHarnessReport(options) {
542
542
  unknowns: limitedUnknowns
543
543
  };
544
544
  }
545
- function validateHarnessReport(report, rootDir) {
545
+ function validateHarnessReport(report, rootDir, options) {
546
546
  const checks = [];
547
547
  const errors = [];
548
548
  const warnings = [];
@@ -713,6 +713,7 @@ function validateHarnessReport(report, rootDir) {
713
713
  }
714
714
  }
715
715
  }
716
+ validateDeterministicFloor(report, options?.deterministicFloor, check, warn);
716
717
  return {
717
718
  schemaVersion: HARNESS_REPORT_VALIDATION_SCHEMA_VERSION,
718
719
  reportId: report?.reportId ?? "unknown",
@@ -724,6 +725,27 @@ function validateHarnessReport(report, rootDir) {
724
725
  warnings
725
726
  };
726
727
  }
728
+ function validateDeterministicFloor(report, floor, check, warn) {
729
+ if (!floor)
730
+ return;
731
+ if (process.env.MODA_HARNESS_FLOOR_CHECK === "0")
732
+ return;
733
+ const graph = isRecord(report?.graph) ? report.graph : {};
734
+ const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord(artifact) && artifact.type === "tool").length : 0;
735
+ const providerCount = safeArrayLength(graph.modelProviders);
736
+ const runtimeAgentCount = safeArrayLength(graph.runtimeAgents);
737
+ if (typeof floor.toolCandidates === "number" && floor.toolCandidates >= 5) {
738
+ const minimumTools = Math.ceil(0.5 * floor.toolCandidates);
739
+ check("floor_tools", toolArtifactCount >= minimumTools, `Deterministic scan found ${floor.toolCandidates} high-confidence tool definition candidate(s); the report graph must include at least ${minimumTools} tool artifact(s) but has ${toolArtifactCount}.`);
740
+ }
741
+ const providerPackages = Array.isArray(floor.providerPackages) ? floor.providerPackages.filter((name) => typeof name === "string" && name.length > 0) : [];
742
+ if (providerPackages.length > 0) {
743
+ check("floor_providers", providerCount > 0, `Manifest dependencies include model-provider package(s) ${providerPackages.join(", ")}, but the report graph.modelProviders is empty.`);
744
+ }
745
+ if (typeof floor.llmCallSites === "number" && floor.llmCallSites > 0 && runtimeAgentCount === 0) {
746
+ warn("floor_agents", `Deterministic scan found ${floor.llmCallSites} LLM call site(s) but the report graph.runtimeAgents is empty; sync will be blocked by the zero-agent gate.`);
747
+ }
748
+ }
727
749
  async function approveHarnessReport(options) {
728
750
  const report = readHarnessReport(options.reportPath);
729
751
  const validation = validateHarnessReport(report, options.rootDir);
@@ -760,9 +782,9 @@ async function approveHarnessReport(options) {
760
782
  writeJson(join(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
761
783
  return { report, validation, approval };
762
784
  }
763
- function loadHarnessReportForSync(rootDir, reportPath) {
785
+ function loadHarnessReportForSync(rootDir, reportPath, options) {
764
786
  const report = readHarnessReport(reportPath);
765
- const validation = validateHarnessReport(report, rootDir);
787
+ const validation = validateHarnessReport(report, rootDir, options);
766
788
  if (validation.status !== "pass") {
767
789
  throw new CliInputError("Harness report validation failed; cannot sync.", validation.errors.slice(0, 5).join(`
768
790
  `));
@@ -1157,7 +1179,7 @@ function firstEvidencePath(item) {
1157
1179
  function stampRemoteSnapshotTruncation(report) {
1158
1180
  const scanRecord = isRecord(report.graph.scan) ? report.graph.scan : {};
1159
1181
  const truncationReasons = Array.isArray(scanRecord.truncationReasons) ? scanRecord.truncationReasons.map(String) : [];
1160
- return {
1182
+ const stamped = {
1161
1183
  ...report,
1162
1184
  graph: {
1163
1185
  ...report.graph,
@@ -1171,6 +1193,10 @@ function stampRemoteSnapshotTruncation(report) {
1171
1193
  }
1172
1194
  }
1173
1195
  };
1196
+ if (stamped.source?.graphHash) {
1197
+ stamped.source = { ...stamped.source, graphHash: hashStableJson(graphForReport(stamped.graph)) };
1198
+ }
1199
+ return stamped;
1174
1200
  }
1175
1201
  function requiresGraphItemConfidence(collection) {
1176
1202
  return collection !== "relationships" && collection !== "unknowns";
@@ -1753,11 +1779,11 @@ import {
1753
1779
  existsSync as existsSync3,
1754
1780
  lstatSync as lstatSync3,
1755
1781
  mkdirSync as mkdirSync3,
1756
- readFileSync as readFileSync3,
1782
+ readFileSync as readFileSync4,
1757
1783
  readdirSync,
1758
1784
  writeFileSync as writeFileSync3
1759
1785
  } from "node:fs";
1760
- import { basename as basename4, delimiter, dirname as dirname4, join as join2, relative as relative3, resolve as resolve4, sep } from "node:path";
1786
+ import { basename as basename4, delimiter, dirname as dirname4, join as join2, relative as relative3, resolve as resolve5, sep } from "node:path";
1761
1787
  import { confirm as confirm2, select } from "@inquirer/prompts";
1762
1788
 
1763
1789
  // src/analyst-tui.ts
@@ -2400,7 +2426,7 @@ var LINE_PATTERNS = [
2400
2426
  kind: "tool_definition",
2401
2427
  signal: "python.tool_decorator",
2402
2428
  confidence: "high",
2403
- pattern: /^\s*@(tool|function_tool|mcp\.tool|app\.tool)\b/,
2429
+ pattern: /^\s*@(tool|function_tool|register_as_tool|\w+\.tool)\b/,
2404
2430
  extensions: PY
2405
2431
  },
2406
2432
  {
@@ -2764,6 +2790,173 @@ function writeHarnessCandidatesReport(report, outPath) {
2764
2790
  return target;
2765
2791
  }
2766
2792
 
2793
+ // src/harness-candidate-promotion.ts
2794
+ import { readFileSync as readFileSync3 } from "node:fs";
2795
+ import { resolve as resolve4 } from "node:path";
2796
+ var PROMOTED_CONFIDENCE = {
2797
+ high: 0.85,
2798
+ medium: 0.6
2799
+ };
2800
+ var MAX_DECORATOR_LOOKAHEAD_LINES = 12;
2801
+ var PY_DEF_NAME = /^\s*(?:async\s+)?def\s+([A-Za-z_]\w*)\s*\(/;
2802
+ var PY_CLASS_NAME = /\bclass\s+([A-Za-z_]\w*)\s*\(/;
2803
+ var JS_ASSIGNMENT_NAME = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/;
2804
+ var JS_PROPERTY_NAME = /^\s*(?:public\s+|readonly\s+)?([A-Za-z_$][\w$]*)\s*:\s*(?:new\s+)?(?:[\w.$]*\.)?(?:tool|functionTool|createTool|dynamicTool|DynamicStructuredTool)\s*\(/;
2805
+ var JS_TRAILING_ASSIGNMENT_NAME = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:new\s+)?$/;
2806
+ var REGISTRY_CALL_NAME = /\b(?:register_tool|registerTool|add_tool)\s*\(\s*["']?([A-Za-z_$][\w$]*)/;
2807
+ var PROMPT_ASSIGNMENT_NAME = /^\s*(?:export\s+)?(?:(?:const|let|var)\s+)?([A-Za-z_$][\w$]*)\s*[:=]/;
2808
+ var MODEL_STRING = /["'`]([\w.:/-]*(?:gpt-|o[134](?:-mini)?\b|claude|gemini|llama|mistral|mixtral|deepseek|qwen|grok|sonnet|opus|haiku|command-r|glm|gemma)[\w.:/-]*)["'`]/i;
2809
+ function candidateEvidence(candidate) {
2810
+ return [{
2811
+ path: candidate.path,
2812
+ line: candidate.startLine,
2813
+ reason: `deterministic detector ${candidate.signal}`
2814
+ }];
2815
+ }
2816
+ function pathStem(path) {
2817
+ return path.replace(/\.[^.]+$/, "");
2818
+ }
2819
+ function readFileLines(rootDir, path, cache) {
2820
+ const cached = cache.get(path);
2821
+ if (cached !== undefined)
2822
+ return cached;
2823
+ let lines;
2824
+ try {
2825
+ lines = readFileSync3(resolve4(rootDir, path), "utf8").split(/\r?\n/);
2826
+ } catch {
2827
+ lines = null;
2828
+ }
2829
+ cache.set(path, lines);
2830
+ return lines;
2831
+ }
2832
+ function extractToolName(candidate, lines) {
2833
+ const line = lines[candidate.startLine - 1] ?? "";
2834
+ if (candidate.signal === "python.tool_decorator") {
2835
+ const end = Math.min(lines.length, candidate.startLine + MAX_DECORATOR_LOOKAHEAD_LINES);
2836
+ for (let i = candidate.startLine;i < end; i += 1) {
2837
+ const match = PY_DEF_NAME.exec(lines[i]);
2838
+ if (match)
2839
+ return match[1];
2840
+ }
2841
+ return;
2842
+ }
2843
+ if (candidate.signal === "python.base_tool_subclass") {
2844
+ return PY_CLASS_NAME.exec(line)?.[1];
2845
+ }
2846
+ if (candidate.signal === "tool_registry.registration") {
2847
+ return REGISTRY_CALL_NAME.exec(line)?.[1];
2848
+ }
2849
+ return JS_ASSIGNMENT_NAME.exec(line)?.[1] ?? JS_PROPERTY_NAME.exec(line)?.[1] ?? (candidate.startLine >= 2 ? JS_TRAILING_ASSIGNMENT_NAME.exec(lines[candidate.startLine - 2])?.[1] : undefined);
2850
+ }
2851
+ function promoteToolCandidate(candidate, rootDir, confidence, fileCache) {
2852
+ const lines = readFileLines(rootDir, candidate.path, fileCache);
2853
+ if (!lines)
2854
+ return;
2855
+ const name = extractToolName(candidate, lines);
2856
+ if (!name)
2857
+ return;
2858
+ return {
2859
+ id: stableId2("tool", `${candidate.path.replace(/\.[^./]+$/, "")}:${name}`),
2860
+ type: "tool",
2861
+ name: titleFromId2(name),
2862
+ scope: "shared",
2863
+ sourcePath: candidate.path,
2864
+ usedByAgentIds: [],
2865
+ confidence,
2866
+ evidence: candidateEvidence(candidate),
2867
+ body: extractToolBody({ path: candidate.path, rootDir }, { name, reason: candidate.signal })
2868
+ };
2869
+ }
2870
+ function promotePromptFileCandidate(candidate, confidence) {
2871
+ return {
2872
+ id: stableId2("prompt", candidate.path),
2873
+ type: "prompt",
2874
+ name: promptNameFromPath(candidate.path),
2875
+ scope: "shared",
2876
+ sourcePath: candidate.path,
2877
+ usedByAgentIds: [],
2878
+ confidence,
2879
+ evidence: candidateEvidence(candidate)
2880
+ };
2881
+ }
2882
+ function promotePromptLiteralCandidate(candidate, rootDir, confidence, fileCache) {
2883
+ const lines = readFileLines(rootDir, candidate.path, fileCache);
2884
+ const assignedName = lines ? PROMPT_ASSIGNMENT_NAME.exec(lines[candidate.startLine - 1] ?? "")?.[1] ?? (candidate.startLine >= 2 ? PROMPT_ASSIGNMENT_NAME.exec(lines[candidate.startLine - 2] ?? "")?.[1] : undefined) : undefined;
2885
+ return {
2886
+ id: stableId2("prompt", `${pathStem(candidate.path)}:L${candidate.startLine}`),
2887
+ type: "prompt",
2888
+ name: assignedName ? titleFromId2(assignedName) : `Inline Prompt (${candidate.path}:${candidate.startLine})`,
2889
+ scope: "shared",
2890
+ usedByAgentIds: [],
2891
+ confidence,
2892
+ evidence: candidateEvidence(candidate)
2893
+ };
2894
+ }
2895
+ function promoteModelConfigCandidate(candidate, rootDir, confidence, fileCache) {
2896
+ const lines = readFileLines(rootDir, candidate.path, fileCache);
2897
+ const sourceLine = lines?.[candidate.startLine - 1] ?? "";
2898
+ const model = MODEL_STRING.exec(sourceLine)?.[1] ?? MODEL_STRING.exec(candidate.excerpt)?.[1];
2899
+ if (!model)
2900
+ return;
2901
+ return {
2902
+ id: stableId2("model_config", `${pathStem(candidate.path)}:L${candidate.startLine}`),
2903
+ type: "model_config",
2904
+ name: model,
2905
+ scope: "shared",
2906
+ usedByAgentIds: [],
2907
+ confidence,
2908
+ evidence: candidateEvidence(candidate)
2909
+ };
2910
+ }
2911
+ function mergePromotedDuplicate(existing, next) {
2912
+ for (const entry of next.evidence) {
2913
+ const already = existing.evidence.some((candidate) => candidate.path === entry.path && candidate.line === entry.line && candidate.reason === entry.reason);
2914
+ if (!already)
2915
+ existing.evidence.push(entry);
2916
+ }
2917
+ existing.confidence = Math.max(existing.confidence, next.confidence);
2918
+ if (!existing.body && next.body)
2919
+ existing.body = next.body;
2920
+ }
2921
+ function promoteHarnessCandidates(report, rootDir) {
2922
+ const fileCache = new Map;
2923
+ const byId = new Map;
2924
+ for (const candidate of report.candidates) {
2925
+ const confidence = PROMOTED_CONFIDENCE[candidate.confidence];
2926
+ if (confidence === undefined)
2927
+ continue;
2928
+ let artifact;
2929
+ switch (candidate.kind) {
2930
+ case "tool_definition":
2931
+ artifact = promoteToolCandidate(candidate, rootDir, confidence, fileCache);
2932
+ break;
2933
+ case "prompt_file":
2934
+ artifact = promotePromptFileCandidate(candidate, confidence);
2935
+ break;
2936
+ case "prompt_literal":
2937
+ if (candidate.confidence === "high") {
2938
+ artifact = promotePromptLiteralCandidate(candidate, rootDir, confidence, fileCache);
2939
+ }
2940
+ break;
2941
+ case "model_config":
2942
+ if (candidate.confidence === "high") {
2943
+ artifact = promoteModelConfigCandidate(candidate, rootDir, confidence, fileCache);
2944
+ }
2945
+ break;
2946
+ default:
2947
+ break;
2948
+ }
2949
+ if (!artifact)
2950
+ continue;
2951
+ const existing = byId.get(artifact.id);
2952
+ if (existing)
2953
+ mergePromotedDuplicate(existing, artifact);
2954
+ else
2955
+ byId.set(artifact.id, artifact);
2956
+ }
2957
+ return [...byId.values()];
2958
+ }
2959
+
2767
2960
  // src/harness-sync-limits.ts
2768
2961
  var MAX_HARNESS_SYNC_GRAPH_BYTES = 2000000;
2769
2962
  function assertHarnessGraphSyncSize(graph) {
@@ -3103,7 +3296,7 @@ var REPORT_JSON_END = "MODA_HARNESS_REPORT_JSON_END";
3103
3296
  var EXTERNAL_ANALYST_ADAPTERS = ["claude", "codex", "cursor"];
3104
3297
  async function runHarnessCommand(context) {
3105
3298
  const subcommand = context.positionals[0] ?? "scan";
3106
- const rootDir = resolve4(context.cwd, context.flags.path ?? ".");
3299
+ const rootDir = resolve5(context.cwd, context.flags.path ?? ".");
3107
3300
  const selectedAgentId = selectedRuntimeAgentId(context.flags);
3108
3301
  const tenant = resolveHarnessTenant(context.flags, context);
3109
3302
  const scanPlan = buildCommandScanPlan(context, rootDir);
@@ -3148,7 +3341,9 @@ async function runHarnessCommand(context) {
3148
3341
  }
3149
3342
  if (context.flags["from-report"] || context.flags.report) {
3150
3343
  const reportPath = resolveHarnessReportPath(rootDir, context.flags["from-report"] ?? context.flags.report);
3151
- const { report, validation, approval, approved } = loadHarnessReportForSync(rootDir, reportPath);
3344
+ const { report, validation, approval, approved } = loadHarnessReportForSync(rootDir, reportPath, {
3345
+ deterministicFloor: computeDeterministicFloor(rootDir)
3346
+ });
3152
3347
  const reportHash = hashHarnessReport(report);
3153
3348
  if (!approved && !context.dryRun) {
3154
3349
  throw new CliInputError("Harness report is not approved; refusing to sync.", "Run `moda harness validate-report`, review the report, then run `moda harness approve --yes` before syncing.");
@@ -3342,11 +3537,13 @@ async function runHarnessCommand(context) {
3342
3537
  elapsed_ms: Date.now() - context.startedAt
3343
3538
  });
3344
3539
  if (context.flags["github-actions"] === "true") {
3345
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-wd1ahhxz.js");
3540
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-dmy8fz54.js");
3346
3541
  const result = await runGithubActionsAnalyze(rootDir, {
3347
3542
  writeReport: (report2) => {
3348
3543
  const normalized = normalizeHarnessReport(report2);
3349
- const validation2 = validateHarnessReport(normalized, rootDir);
3544
+ const validation2 = validateHarnessReport(normalized, rootDir, {
3545
+ deterministicFloor: computeDeterministicFloor(rootDir)
3546
+ });
3350
3547
  writeHarnessReportArtifacts({
3351
3548
  rootDir,
3352
3549
  report: normalized,
@@ -3434,7 +3631,9 @@ async function runHarnessCommand(context) {
3434
3631
  case "validate-report": {
3435
3632
  const reportPath = resolveHarnessReportPath(rootDir, context.positionals[1] ?? context.flags.report);
3436
3633
  const report = readHarnessReport(reportPath);
3437
- const validation = validateHarnessReport(report, rootDir);
3634
+ const validation = validateHarnessReport(report, rootDir, {
3635
+ deterministicFloor: computeDeterministicFloor(rootDir)
3636
+ });
3438
3637
  if (!context.dryRun) {
3439
3638
  writeHarnessReportArtifacts({
3440
3639
  rootDir,
@@ -3479,7 +3678,7 @@ async function runHarnessCommand(context) {
3479
3678
  throw new CliInputError("Missing output path for --out.", "Usage: moda harness candidates --out=<file>");
3480
3679
  }
3481
3680
  if (outFlag && !context.dryRun) {
3482
- written.push(writeHarnessCandidatesReport(report, resolve4(context.cwd, outFlag)));
3681
+ written.push(writeHarnessCandidatesReport(report, resolve5(context.cwd, outFlag)));
3483
3682
  }
3484
3683
  writeHumanProgress(context, `Harness candidates: ${report.stats.candidateCount} candidates across ${report.stats.filesWithCandidates} files (${report.stats.filesScanned} scanned, ${report.stats.filesPerSecond.toLocaleString()} files/sec).`);
3485
3684
  context.output.writeData({ scanPlan, ...report, written, dryRun: context.dryRun });
@@ -3740,7 +3939,9 @@ ${result.stderr}`, 1200);
3740
3939
  elapsed_ms: Date.now() - options.context.startedAt
3741
3940
  });
3742
3941
  analystTui?.setValidation("running");
3743
- const validation = validateHarnessReport(report, options.rootDir);
3942
+ const validation = validateHarnessReport(report, options.rootDir, {
3943
+ deterministicFloor: computeDeterministicFloor(options.rootDir)
3944
+ });
3744
3945
  analystTui?.setValidation(validation.status === "pass" ? "pass" : "fail");
3745
3946
  if (!analystTui)
3746
3947
  writeHumanProgress(options.context, `Moda Analyst: validation ${validation.status}.`);
@@ -3790,7 +3991,7 @@ function buildExternalAnalystEnv(env) {
3790
3991
  return next;
3791
3992
  }
3792
3993
  function readExternalAnalystReport(rootDir, result, adapter) {
3793
- const reportPath = resolve4(rootDir, HARNESS_REPORT_PATH);
3994
+ const reportPath = resolve5(rootDir, HARNESS_REPORT_PATH);
3794
3995
  if (existsSync3(reportPath))
3795
3996
  return repairExternalAnalystReport(readHarnessReport(reportPath), rootDir);
3796
3997
  const output = [
@@ -3805,7 +4006,7 @@ function readExternalAnalystReport(rootDir, result, adapter) {
3805
4006
  return parsed;
3806
4007
  let debugHint = "";
3807
4008
  try {
3808
- const debugPath = resolve4(rootDir, MODA_DIR, "analyst-debug.log");
4009
+ const debugPath = resolve5(rootDir, MODA_DIR, "analyst-debug.log");
3809
4010
  mkdirSync3(dirname4(debugPath), { recursive: true });
3810
4011
  writeFileSync3(debugPath, [
3811
4012
  `# analyst=${adapter} status=${result.status} signal=${result.signal} timedOut=${result.timedOut} maxBufferExceeded=${result.maxBufferExceeded}`,
@@ -3865,10 +4066,96 @@ function parseHarnessReportCandidate(candidate) {
3865
4066
  function repairExternalAnalystReport(report, rootDir) {
3866
4067
  const normalized = normalizeHarnessReport(report);
3867
4068
  repairCitationLines(normalized, rootDir);
4069
+ mergeDeterministicArtifacts(normalized.graph, rootDir);
3868
4070
  hydratePromptAndToolBodies(normalized.graph, rootDir);
4071
+ deriveRelationshipsFromGraph(normalized.graph);
3869
4072
  repairReportSummaryCounts(normalized);
4073
+ if (normalized.source?.graphHash) {
4074
+ normalized.source.graphHash = hashStableJson(graphForReport(normalized.graph));
4075
+ }
3870
4076
  return normalized;
3871
4077
  }
4078
+ function deriveRelationshipsFromGraph(graph) {
4079
+ if (process.env.MODA_HARNESS_DERIVE_RELATIONSHIPS === "0")
4080
+ return { added: 0 };
4081
+ const agentIds = new Set(graph.runtimeAgents.map((agent) => agent.id));
4082
+ const artifactsById = new Map(graph.artifacts.map((artifact) => [artifact.id, artifact]));
4083
+ const seen = new Set(graph.relationships.map((rel) => `${rel.type}|${rel.from}|${rel.to}`));
4084
+ let added = 0;
4085
+ const push = (type, from, to, evidence) => {
4086
+ const key = `${type}|${from}|${to}`;
4087
+ if (seen.has(key))
4088
+ return;
4089
+ seen.add(key);
4090
+ graph.relationships.push({ type, from, to, evidence });
4091
+ added += 1;
4092
+ };
4093
+ for (const artifact of graph.artifacts) {
4094
+ const evidence = artifact.evidence?.length ? [artifact.evidence[0]] : [{ path: artifact.sourcePath ?? ".", reason: "derived from artifact linkage fields" }];
4095
+ if (artifact.ownedByAgentId && agentIds.has(artifact.ownedByAgentId)) {
4096
+ push("owns_artifact", artifact.ownedByAgentId, artifact.id, evidence);
4097
+ }
4098
+ for (const agentId of artifact.usedByAgentIds ?? []) {
4099
+ if (agentIds.has(agentId))
4100
+ push("uses_artifact", agentId, artifact.id, evidence);
4101
+ }
4102
+ }
4103
+ for (const agent of graph.runtimeAgents) {
4104
+ for (const artifactId of agent.artifactIds ?? []) {
4105
+ const artifact = artifactsById.get(artifactId);
4106
+ if (!artifact)
4107
+ continue;
4108
+ const evidence = agent.evidence?.length ? [agent.evidence[0]] : [{ path: agent.root ?? ".", reason: "derived from agent artifactIds" }];
4109
+ push("uses_artifact", agent.id, artifact.id, evidence);
4110
+ }
4111
+ }
4112
+ return { added };
4113
+ }
4114
+ function mergeDeterministicArtifacts(graph, rootDir) {
4115
+ if (process.env.MODA_HARNESS_DETERMINISTIC_MERGE === "0")
4116
+ return { added: 0 };
4117
+ let promoted;
4118
+ try {
4119
+ promoted = promoteHarnessCandidates(detectHarnessCandidates(rootDir), rootDir);
4120
+ } catch {
4121
+ return { added: 0 };
4122
+ }
4123
+ let added = 0;
4124
+ for (const artifact of promoted) {
4125
+ if (graphCoversPromotedArtifact(graph, artifact))
4126
+ continue;
4127
+ graph.artifacts.push(artifact);
4128
+ added += 1;
4129
+ }
4130
+ return { added };
4131
+ }
4132
+ function artifactSourceKey(artifact) {
4133
+ if (artifact.sourcePath)
4134
+ return normalizePath3(artifact.sourcePath);
4135
+ const pinned = Array.isArray(artifact.evidence) ? artifact.evidence.find((entry) => typeof entry.path === "string" && entry.path.length > 0) : undefined;
4136
+ return pinned ? normalizePath3(pinned.path) : undefined;
4137
+ }
4138
+ function graphCoversPromotedArtifact(graph, promoted) {
4139
+ const promotedName = promoted.name.toLowerCase();
4140
+ const promotedLines = promoted.evidence.filter((entry) => typeof entry.line === "number");
4141
+ const promotedSource = artifactSourceKey(promoted);
4142
+ const isWholeFilePrompt = promoted.type === "prompt" && Boolean(promoted.sourcePath);
4143
+ return graph.artifacts.some((existing) => {
4144
+ if (existing.id === promoted.id)
4145
+ return true;
4146
+ if (existing.type === promoted.type && existing.name?.toLowerCase() === promotedName) {
4147
+ const existingSource = artifactSourceKey(existing);
4148
+ if (!existingSource || !promotedSource || existingSource === promotedSource)
4149
+ return true;
4150
+ }
4151
+ if (existing.type === promoted.type && Array.isArray(existing.evidence) && existing.evidence.some((entry) => typeof entry.line === "number" && promotedLines.some((line) => line.path === entry.path && line.line === entry.line))) {
4152
+ return true;
4153
+ }
4154
+ if (isWholeFilePrompt && existing.sourcePath === promoted.sourcePath)
4155
+ return true;
4156
+ return false;
4157
+ });
4158
+ }
3872
4159
  function repairReportSummaryCounts(report) {
3873
4160
  const graph = report.graph;
3874
4161
  report.summary = {
@@ -3892,12 +4179,12 @@ function repairCitationLines(report, rootDir) {
3892
4179
  if (citation.type !== "file" && citation.type !== "generated_doc" || !citation.path || !citation.excerpt)
3893
4180
  continue;
3894
4181
  const excerpt = citation.excerpt;
3895
- const candidatePath = resolve4(rootDir, citation.path);
4182
+ const candidatePath = resolve5(rootDir, citation.path);
3896
4183
  if (!existsSync3(candidatePath))
3897
4184
  continue;
3898
4185
  let text;
3899
4186
  try {
3900
- text = readFileSync3(candidatePath, "utf8");
4187
+ text = readFileSync4(candidatePath, "utf8");
3901
4188
  } catch {
3902
4189
  continue;
3903
4190
  }
@@ -4524,6 +4811,7 @@ function renderExternalHarnessAnalystPrompt(options) {
4524
4811
  "Artifacts must include id, type, name, scope, usedByAgentIds, confidence, evidence, and optional sourcePath/contentHash/ownedByAgentId. Emit one artifact PER tool, PER prompt (including each inline system prompt), PER guardrail, and PER eval — enumerate them all; artifact types are entrypoint, prompt, tool, skill, eval, retrieval_index, memory, guardrail, model_config, unknown.",
4525
4812
  "Do NOT transcribe prompt/tool text into `body` when it exists verbatim in the repo — pin its exact location instead (sourcePath for a dedicated file, or evidence [{ path, line, reason }] at the definition line) and Moda hydrates `body` locally from that location after validation. Set `body` inline ONLY for content that cannot be recovered from one pinned location: dynamically assembled prompts, fragments concatenated at runtime, or values you had to derive. Short bodies for `guardrail` (the rule), `eval` (what it asserts), and `model_config` (the config values) are still welcome. Redact any secret literals.",
4526
4813
  "For every `prompt` and `tool` artifact, at least one evidence entry MUST pin an exact source location so downstream optimization can retrieve the item text: carry a numeric `line` (the definition line, or the first line of the range) — evidence: [{ path, line, reason }]. The only exception is an artifact whose entire dedicated file is the text (e.g. a standalone prompt file); set its sourcePath and cite that same file and no line is required. When a single file holds multiple inline prompts or tool definitions, give each its own distinct line — a bare { path, reason } is a defect there.",
4814
+ "LINKAGE IS MANDATORY, not optional metadata: every artifact you author must carry usedByAgentIds naming each runtime agent that uses it (and ownedByAgentId when a single agent owns it), and every runtime agent's artifactIds must list its prompts, tools, and evals. Moda derives uses_artifact/owns_artifact edges from these fields after your run, so empty linkage means a disconnected map. Reserve explicit `relationships` entries for what linkage fields cannot express: covered_by_eval, runs_in_environment, invoked_by_channel, deployed_as, shares_artifact_with, resolves_identity_to, emits_telemetry.",
4527
4815
  "Agent families must include id, name, runtimeAgentIds, sharedArtifactIds, confidence, evidence.",
4528
4816
  "Frameworks/providers must include packageNames and runtimeAgentIds arrays; providers also include envVars and modelHints arrays.",
4529
4817
  "Unknowns in graph must include id, question, candidateIds, evidence. Report-level unknowns must include id, question, citationIds.",
@@ -4634,7 +4922,7 @@ function renderAnalystCandidateLines(report) {
4634
4922
  const lines = [
4635
4923
  "",
4636
4924
  `Deterministic candidate inventory (${report.candidates.length} candidates across ${report.stats.filesWithCandidates} files, scanned in ${report.stats.durationMs}ms):`,
4637
- "Each entry is path:line [detector] excerpt. Verify each candidate against the real file, then represent every confirmed tool/prompt/eval/model-config as a graph artifact pinned to that location. Candidates are pattern matches — drop false positives, and investigate what patterns cannot see (agent topology, relationships, prompts assembled at runtime). Do not cite this list as evidence; cite the files."
4925
+ "Each entry is path:line [detector] excerpt. Candidates are pattern matches — verify each against the real file and drop false positives. Verified deterministic candidates are also auto-merged into the graph after your run, so prioritize (1) agents, topology, and relationships, (2) verifying or refuting these candidates (list refuted ones in unknowns), and (3) artifacts the patterns cannot see (runtime agents, prompts assembled at runtime, cross-file wiring) — rather than re-listing every candidate as an artifact. Do not cite this list as evidence; cite the files."
4638
4926
  ];
4639
4927
  for (const [kind, label] of Object.entries(SEED_CANDIDATE_KIND_LABELS)) {
4640
4928
  const bucket = byKind.get(kind);
@@ -4786,7 +5074,7 @@ function truncateText(value, maxLength) {
4786
5074
  return compact.length > maxLength ? `${compact.slice(0, maxLength - 1)}...` : compact;
4787
5075
  }
4788
5076
  function scanHarness(options = {}) {
4789
- const rootDir = resolve4(options.rootDir ?? process.cwd());
5077
+ const rootDir = resolve5(options.rootDir ?? process.cwd());
4790
5078
  const skipped = [];
4791
5079
  const files = inventoryFiles(rootDir, skipped);
4792
5080
  const annotations = files.flatMap((file) => parseAnnotations(file));
@@ -4888,7 +5176,7 @@ function collectSnapshotInputs(rootDir, maxFiles, maxTextBytes) {
4888
5176
  let text = "";
4889
5177
  if (isTextFile(path)) {
4890
5178
  try {
4891
- text = readFileSync3(current, "utf8");
5179
+ text = readFileSync4(current, "utf8");
4892
5180
  inventoryTextBytes += Buffer.byteLength(text, "utf8");
4893
5181
  } catch {
4894
5182
  return;
@@ -4901,7 +5189,7 @@ function collectSnapshotInputs(rootDir, maxFiles, maxTextBytes) {
4901
5189
  return inputs;
4902
5190
  }
4903
5191
  function buildSourceSnapshot(rootDir) {
4904
- const root = resolve4(rootDir);
5192
+ const root = resolve5(rootDir);
4905
5193
  const maxFiles = numericEnv("MODA_HARNESS_MAX_FILES", DEFAULT_MAX_INVENTORY_FILES);
4906
5194
  const maxTextBytes = numericEnv("MODA_HARNESS_MAX_TEXT_BYTES", DEFAULT_MAX_INVENTORY_TEXT_BYTES);
4907
5195
  const inputs = collectSnapshotInputs(root, maxFiles, maxTextBytes);
@@ -5128,7 +5416,7 @@ function readHarnessSyncState(rootDir) {
5128
5416
  if (!existsSync3(path))
5129
5417
  return;
5130
5418
  try {
5131
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
5419
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
5132
5420
  if (!isRecord3(parsed) || parsed.schemaVersion !== "sync_state.v0.1") {
5133
5421
  return;
5134
5422
  }
@@ -5245,7 +5533,7 @@ function findFresherHarnessReports(rootDir) {
5245
5533
  return fresher.sort((a, b) => b.mtimeMs - a.mtimeMs);
5246
5534
  }
5247
5535
  function warnFresherSubdirHarnessReports(context, rootDir, explicitReport) {
5248
- const explicitReportPath = explicitReport && explicitReport !== "true" ? normalizePath3(relative3(rootDir, resolve4(rootDir, explicitReport))) : undefined;
5536
+ const explicitReportPath = explicitReport && explicitReport !== "true" ? normalizePath3(relative3(rootDir, resolve5(rootDir, explicitReport))) : undefined;
5249
5537
  if (explicitReportPath && explicitReportPath !== normalizePath3(HARNESS_REPORT_PATH))
5250
5538
  return;
5251
5539
  let fresher;
@@ -5262,7 +5550,7 @@ function warnFresherSubdirHarnessReports(context, rootDir, explicitReport) {
5262
5550
  writeHarnessNotice(context, `A fresher harness report exists at ${newest.path} (this directory has ${rootState}). If you meant that report, re-run with --path=${subdir}.`);
5263
5551
  }
5264
5552
  function readHarnessGraph(manifestPath) {
5265
- return JSON.parse(readFileSync3(manifestPath, "utf8"));
5553
+ return JSON.parse(readFileSync4(manifestPath, "utf8"));
5266
5554
  }
5267
5555
  function writeHarnessArtifacts(graph, rootDir, options = {}) {
5268
5556
  const modaDir = join2(rootDir, MODA_DIR);
@@ -5439,7 +5727,7 @@ function explainAgent(graph, agent) {
5439
5727
  }
5440
5728
  function inventoryFiles(rootDir, skipped, options = {}) {
5441
5729
  const files = [];
5442
- const rootReal = resolve4(rootDir);
5730
+ const rootReal = resolve5(rootDir);
5443
5731
  const maxFiles = numericEnv("MODA_HARNESS_MAX_FILES", DEFAULT_MAX_INVENTORY_FILES);
5444
5732
  const maxTextBytes = numericEnv("MODA_HARNESS_MAX_TEXT_BYTES", DEFAULT_MAX_INVENTORY_TEXT_BYTES);
5445
5733
  let textBytes = 0;
@@ -5493,7 +5781,7 @@ function inventoryFiles(rootDir, skipped, options = {}) {
5493
5781
  recordSkipped(skipped, { path: rel, reason: "scan text budget exceeded" });
5494
5782
  } else {
5495
5783
  try {
5496
- item.text = readFileSync3(current, "utf8");
5784
+ item.text = readFileSync4(current, "utf8");
5497
5785
  textBytes += Buffer.byteLength(item.text, "utf8");
5498
5786
  } catch {
5499
5787
  recordSkipped(skipped, { path: rel, reason: "text read failed" });
@@ -5800,7 +6088,7 @@ function maybeAddPromptArtifact(graph, file) {
5800
6088
  if (!isPrompt)
5801
6089
  return;
5802
6090
  const id = stableId2("prompt", file.path);
5803
- const text = file.text ?? readFileSync3(file.absPath, "utf8");
6091
+ const text = file.text ?? readFileSync4(file.absPath, "utf8");
5804
6092
  upsertArtifact(graph, {
5805
6093
  id,
5806
6094
  type: "prompt",
@@ -6027,12 +6315,27 @@ function detectToolDefinitions(file) {
6027
6315
  const pyDecorated = /@function_tool[^\n]*\n(?:[ \t]*@[^\n]*\n)*[ \t]*(?:async[ \t]+)?def[ \t]+([A-Za-z_]\w*)/g;
6028
6316
  for (const match of text.matchAll(pyDecorated))
6029
6317
  found.set(match[1], "@function_tool-decorated function");
6318
+ const pyToolDecorated = /@((?:[A-Za-z_]\w*\.)?tool|register_as_tool)\b[^\n]*\n(?:[ \t]*@[^\n]*\n)*[ \t]*(?:async[ \t]+)?def[ \t]+([A-Za-z_]\w*)/g;
6319
+ for (const match of text.matchAll(pyToolDecorated))
6320
+ found.set(match[2], `@${match[1]}-decorated function`);
6321
+ const pyToolClass = /class[ \t]+([A-Za-z_]\w*)\s*\(\s*(?:[A-Za-z_]\w*\.)?(BaseTool|StructuredTool)\b/g;
6322
+ for (const match of text.matchAll(pyToolClass))
6323
+ found.set(match[1], `${match[2]} subclass`);
6030
6324
  const tsFactory = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:[\w.]*\.)?(?:tool|functionTool|createTool|dynamicTool)\s*\(/g;
6031
6325
  for (const match of text.matchAll(tsFactory))
6032
6326
  found.set(match[1], "tool factory assignment");
6033
- const tsNewTool = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*new\s+(?:Dynamic|Structured)?Tool\b/g;
6327
+ const tsNewTool = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*new\s+(?:DynamicStructured|Dynamic|Structured)?Tool\b/g;
6034
6328
  for (const match of text.matchAll(tsNewTool))
6035
6329
  found.set(match[1], "tool constructor assignment");
6330
+ const tsInlineToolCtor = /(?:^|[(,[]|return\s)\s*(?:new\s+)?(?:DynamicStructured|Dynamic|Structured)Tool\s*\(\s*\{[^{}]*?\bname\s*:\s*["'`]([\w .:-]+)["'`]/gm;
6331
+ for (const match of text.matchAll(tsInlineToolCtor))
6332
+ found.set(match[1], "tool constructor call");
6333
+ const registryCall = /\b(register_tool|registerTool|add_tool|ToolRegistry)\s*\(\s*\[?\s*(?:name\s*=\s*)?(?:["']([\w .:-]+)["']|([A-Za-z_$][\w$]*))/g;
6334
+ for (const match of text.matchAll(registryCall)) {
6335
+ const name = match[2] ?? match[3];
6336
+ if (name)
6337
+ found.set(name, `${match[1]} registration`);
6338
+ }
6036
6339
  for (const match of text.matchAll(HOSTED_TOOL_CALL))
6037
6340
  found.set(match[1], `hosted tool ${match[1]}`);
6038
6341
  return [...found.entries()].map(([name, reason]) => ({ name, reason }));
@@ -6048,7 +6351,7 @@ function maybeAddToolArtifact(graph, file) {
6048
6351
  for (const tool of tools) {
6049
6352
  const body = extractToolBody(file, tool);
6050
6353
  upsertArtifact(graph, {
6051
- id: stableId2("tool", `${file.path}:${tool.name}`),
6354
+ id: stableId2("tool", `${file.path.replace(/\.[^./]+$/, "")}:${tool.name}`),
6052
6355
  type: "tool",
6053
6356
  name: titleFromId2(tool.name),
6054
6357
  scope: "agent",
@@ -6156,6 +6459,55 @@ var FRAMEWORK_CATALOG = [
6156
6459
  kind: "agent_framework",
6157
6460
  packageNames: ["pydantic-ai", "pydantic_ai"],
6158
6461
  patterns: [/pydantic_ai/i]
6462
+ },
6463
+ {
6464
+ id: "framework_langgraph",
6465
+ name: "LangGraph",
6466
+ kind: "agent_framework",
6467
+ packageNames: ["langgraph", "@langchain/langgraph"],
6468
+ patterns: [/\blanggraph\b/i, /\bStateGraph\s*\(/]
6469
+ },
6470
+ {
6471
+ id: "framework_autogen",
6472
+ name: "AutoGen",
6473
+ kind: "agent_framework",
6474
+ packageNames: ["autogen", "pyautogen", "autogen-agentchat"],
6475
+ patterns: [/\bautogen\b/i]
6476
+ },
6477
+ {
6478
+ id: "framework_dspy",
6479
+ name: "DSPy",
6480
+ kind: "agent_framework",
6481
+ packageNames: ["dspy", "dspy-ai"],
6482
+ patterns: [/\bdspy\b/i]
6483
+ },
6484
+ {
6485
+ id: "framework_semantic_kernel",
6486
+ name: "Semantic Kernel",
6487
+ kind: "agent_framework",
6488
+ packageNames: ["semantic-kernel"],
6489
+ patterns: [/semantic[-_]?kernel/i]
6490
+ },
6491
+ {
6492
+ id: "framework_smolagents",
6493
+ name: "Smolagents",
6494
+ kind: "agent_framework",
6495
+ packageNames: ["smolagents"],
6496
+ patterns: [/\bsmolagents\b/i]
6497
+ },
6498
+ {
6499
+ id: "framework_llama_index",
6500
+ name: "LlamaIndex",
6501
+ kind: "agent_framework",
6502
+ packageNames: ["llama-index", "llamaindex"],
6503
+ patterns: [/llama[-_]?index/i]
6504
+ },
6505
+ {
6506
+ id: "framework_claude_agent_sdk",
6507
+ name: "Claude Agent SDK",
6508
+ kind: "agent_framework",
6509
+ packageNames: ["@anthropic-ai/claude-agent-sdk", "claude-agent-sdk"],
6510
+ patterns: [/claude[-_]agent[-_]sdk/i]
6159
6511
  }
6160
6512
  ];
6161
6513
  var PROVIDER_CATALOG = [
@@ -6206,6 +6558,46 @@ var PROVIDER_CATALOG = [
6206
6558
  packageNames: ["ollama"],
6207
6559
  envVars: ["OLLAMA_HOST"],
6208
6560
  patterns: [/\bollama\b/i]
6561
+ },
6562
+ {
6563
+ id: "provider_litellm",
6564
+ name: "LiteLLM",
6565
+ kind: "model_provider",
6566
+ packageNames: ["litellm"],
6567
+ envVars: [],
6568
+ patterns: [/\blitellm\b/i]
6569
+ },
6570
+ {
6571
+ id: "provider_aws_bedrock",
6572
+ name: "AWS Bedrock",
6573
+ kind: "model_provider",
6574
+ packageNames: ["@aws-sdk/client-bedrock-runtime"],
6575
+ envVars: ["AWS_BEARER_TOKEN_BEDROCK"],
6576
+ patterns: [/bedrock-runtime/i, /\bBedrockRuntimeClient\b/, /\bInvokeModelCommand\b/, /\binvoke_model\s*\(/]
6577
+ },
6578
+ {
6579
+ id: "provider_openrouter",
6580
+ name: "OpenRouter",
6581
+ kind: "model_provider",
6582
+ packageNames: ["openrouter", "@openrouter/ai-sdk-provider"],
6583
+ envVars: ["OPENROUTER_API_KEY"],
6584
+ patterns: [/openrouter\.ai/i, /\bopenrouter\b/i, /OPENROUTER_API_KEY/]
6585
+ },
6586
+ {
6587
+ id: "provider_xai",
6588
+ name: "xAI",
6589
+ kind: "model_provider",
6590
+ packageNames: ["xai-sdk"],
6591
+ envVars: ["XAI_API_KEY"],
6592
+ patterns: [/\bgrok-[\w.-]+/i, /api\.x\.ai/i, /XAI_API_KEY/]
6593
+ },
6594
+ {
6595
+ id: "provider_groq",
6596
+ name: "Groq",
6597
+ kind: "model_provider",
6598
+ packageNames: ["groq", "groq-sdk"],
6599
+ envVars: ["GROQ_API_KEY"],
6600
+ patterns: [/\bgroq\b/i]
6209
6601
  }
6210
6602
  ];
6211
6603
  function detectAgentFrameworks(files) {
@@ -6247,6 +6639,59 @@ function detectModelProviders(files) {
6247
6639
  }
6248
6640
  return providers.sort((a, b) => a.id.localeCompare(b.id));
6249
6641
  }
6642
+ function computeDeterministicFloor(rootDir) {
6643
+ try {
6644
+ const candidateReport = detectHarnessCandidates(rootDir);
6645
+ let toolCandidates = 0;
6646
+ let promptCandidates = 0;
6647
+ let llmCallSites = 0;
6648
+ for (const candidate of candidateReport.candidates) {
6649
+ if (candidate.kind === "tool_definition" && candidate.confidence === "high") {
6650
+ toolCandidates += 1;
6651
+ } else if ((candidate.kind === "prompt_file" || candidate.kind === "prompt_literal") && candidate.confidence === "high") {
6652
+ promptCandidates += 1;
6653
+ } else if (candidate.kind === "llm_call_site") {
6654
+ llmCallSites += 1;
6655
+ }
6656
+ }
6657
+ return {
6658
+ toolCandidates,
6659
+ promptCandidates,
6660
+ llmCallSites,
6661
+ providerPackages: detectProviderPackagesInManifests(rootDir)
6662
+ };
6663
+ } catch {
6664
+ return { toolCandidates: 0, promptCandidates: 0, llmCallSites: 0, providerPackages: [] };
6665
+ }
6666
+ }
6667
+ function detectProviderPackagesInManifests(rootDir) {
6668
+ const present = new Set;
6669
+ const packageJsonPath = join2(rootDir, "package.json");
6670
+ if (existsSync3(packageJsonPath)) {
6671
+ try {
6672
+ const deps = packageDependencyNames(readFileSync4(packageJsonPath, "utf8"));
6673
+ for (const provider of PROVIDER_CATALOG) {
6674
+ for (const name of provider.packageNames) {
6675
+ if (deps.has(name))
6676
+ present.add(name);
6677
+ }
6678
+ }
6679
+ } catch {}
6680
+ }
6681
+ const pyprojectPath = join2(rootDir, "pyproject.toml");
6682
+ if (existsSync3(pyprojectPath)) {
6683
+ try {
6684
+ const deps = pyprojectDependencyNames(readFileSync4(pyprojectPath, "utf8"));
6685
+ for (const provider of PROVIDER_CATALOG) {
6686
+ for (const name of provider.packageNames) {
6687
+ if (deps.has(name.toLowerCase()))
6688
+ present.add(name);
6689
+ }
6690
+ }
6691
+ } catch {}
6692
+ }
6693
+ return [...present].sort();
6694
+ }
6250
6695
  function detectionEvidence(files, packageNames, patterns) {
6251
6696
  const evidence = [];
6252
6697
  for (const file of files) {
@@ -6328,7 +6773,7 @@ function pyprojectDependencyNames(text) {
6328
6773
  }
6329
6774
  function modelHintsForProvider(providerId, files) {
6330
6775
  const hints = new Set;
6331
- const pattern = providerId === "provider_anthropic" ? /\bclaude-[A-Za-z0-9_.-]+/g : providerId === "provider_openai" ? /\bgpt-[A-Za-z0-9_.-]+/g : providerId === "provider_google" ? /\bgemini-[A-Za-z0-9_.-]+/g : null;
6776
+ const pattern = providerId === "provider_anthropic" ? /\bclaude-[A-Za-z0-9_.-]+/g : providerId === "provider_openai" ? /\bgpt-[A-Za-z0-9_.-]+/g : providerId === "provider_google" ? /\bgemini-[A-Za-z0-9_.-]+/g : providerId === "provider_xai" ? /\bgrok-[A-Za-z0-9_.-]+/g : null;
6332
6777
  if (!pattern)
6333
6778
  return [];
6334
6779
  for (const file of files) {
@@ -6619,7 +7064,7 @@ function upsertDeployment(graph, next) {
6619
7064
  }
6620
7065
  function contentHashForAnnotatedSource(sourcePath, annotation) {
6621
7066
  const bodyAnnotation = annotation;
6622
- const rootCandidate = resolve4(annotation.rootDir, sourcePath);
7067
+ const rootCandidate = resolve5(annotation.rootDir, sourcePath);
6623
7068
  const candidates = [
6624
7069
  rootCandidate,
6625
7070
  ...annotatedSourceCandidates(sourcePath, bodyAnnotation).filter((c) => c !== rootCandidate)
@@ -6653,7 +7098,7 @@ function readSmallTextFileForHash(absPath, rootDir) {
6653
7098
  if (!isTextFile(rel))
6654
7099
  return;
6655
7100
  try {
6656
- return readFileSync3(absPath, "utf8");
7101
+ return readFileSync4(absPath, "utf8");
6657
7102
  } catch {
6658
7103
  return;
6659
7104
  }
@@ -6773,7 +7218,7 @@ function readRemoteRunState(rootDir) {
6773
7218
  if (!existsSync4(path))
6774
7219
  return;
6775
7220
  try {
6776
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
7221
+ const parsed = JSON.parse(readFileSync5(path, "utf8"));
6777
7222
  if (!parsed || typeof parsed.run_id !== "string" || parsed.run_id.length === 0)
6778
7223
  return;
6779
7224
  return {
@@ -6928,7 +7373,9 @@ function finalizeRemoteAnalyzeResult(options) {
6928
7373
  if (options.snapshotTruncated) {
6929
7374
  report = stampRemoteSnapshotTruncation(report);
6930
7375
  }
6931
- const validation = validateHarnessReport(report, rootDir);
7376
+ const validation = validateHarnessReport(report, rootDir, {
7377
+ deterministicFloor: computeDeterministicFloor(rootDir)
7378
+ });
6932
7379
  context.output.writeEvent({
6933
7380
  event: "progress",
6934
7381
  phase: "validation",
@@ -11,7 +11,7 @@ import {
11
11
  renderStatusHuman,
12
12
  scanHarness,
13
13
  validateHarnessReport
14
- } from "./cli-793dhrgb.js";
14
+ } from "./cli-5e0rd8mf.js";
15
15
  import {
16
16
  isAuthSessionValid,
17
17
  loadAuthSession
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  runPromptsCommand,
9
9
  runSkillsCommand,
10
10
  runStatusCommand
11
- } from "./cli-n5qz81m0.js";
11
+ } from "./cli-b2ktsnmh.js";
12
12
  import {
13
13
  ApiError,
14
14
  HARNESS_REPORT_APPROVAL_PATH,
@@ -29,7 +29,7 @@ import {
29
29
  summarizeHarness,
30
30
  terminalStyles,
31
31
  validateHarnessReport
32
- } from "./cli-793dhrgb.js";
32
+ } from "./cli-5e0rd8mf.js";
33
33
  import {
34
34
  authFetch,
35
35
  clearAuthSession,
@@ -6496,7 +6496,7 @@ var commandRegistry = createCommandRegistry([
6496
6496
  resetApiRequestCountBeforeRun: true,
6497
6497
  telemetry: "result",
6498
6498
  handler: async (context) => {
6499
- const { runInit } = await import("./index-djg29jhz.js");
6499
+ const { runInit } = await import("./index-6twsawta.js");
6500
6500
  if (context.outputMode === "agent-stream") {
6501
6501
  context.output.writeEvent({
6502
6502
  event: "started",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildSourceSnapshot
3
- } from "./cli-793dhrgb.js";
3
+ } from "./cli-5e0rd8mf.js";
4
4
  import"./cli-59yacef3.js";
5
5
 
6
6
  // src/harness-github-actions.ts
@@ -9,7 +9,7 @@ import {
9
9
  runPromptSync,
10
10
  runSkillSync,
11
11
  upsertSkillManifestRecord
12
- } from "./cli-n5qz81m0.js";
12
+ } from "./cli-b2ktsnmh.js";
13
13
  import {
14
14
  codingAgentDisplayName,
15
15
  describeCodingAgentEvent,
@@ -27,7 +27,7 @@ import {
27
27
  runHarnessCommand,
28
28
  startRemoteAnalyze,
29
29
  stripAnsi
30
- } from "./cli-793dhrgb.js";
30
+ } from "./cli-5e0rd8mf.js";
31
31
  import {
32
32
  selectTenantAndCreateKey
33
33
  } from "./cli-pq6rte0w.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.28.6",
3
+ "version": "1.29.1",
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-08-28T19:57:51.453Z",
4
- "cli_version": "1.28.6",
3
+ "bundled_at": "2026-08-29T04:58:53.183Z",
4
+ "cli_version": "1.29.1",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-node-anthropic",