@moda-ai/cli 1.30.1 → 1.31.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.
@@ -284,6 +284,7 @@ var HARNESS_REPORT_PATH = join(".moda", "harness-report.json");
284
284
  var HARNESS_REPORT_MARKDOWN_PATH = join(".moda", "HARNESS_REPORT.md");
285
285
  var HARNESS_REPORT_VALIDATION_PATH = join(".moda", "harness-report-validation.json");
286
286
  var HARNESS_REPORT_APPROVAL_PATH = join(".moda", "harness-report-approval.json");
287
+ var HARNESS_ANALYZE_CONCERNS = ["topology", "tools", "prompts", "evals"];
287
288
  var HARNESS_GRAPH_ARRAY_FIELDS = [
288
289
  "agentFamilies",
289
290
  "runtimeAgents",
@@ -713,7 +714,7 @@ function validateHarnessReport(report, rootDir, options) {
713
714
  }
714
715
  }
715
716
  }
716
- validateDeterministicFloor(report, options?.deterministicFloor, check, warn);
717
+ validateDeterministicFloor(report, options?.deterministicFloor, check, warn, options?.concern);
717
718
  return {
718
719
  schemaVersion: HARNESS_REPORT_VALIDATION_SCHEMA_VERSION,
719
720
  reportId: report?.reportId ?? "unknown",
@@ -725,24 +726,26 @@ function validateHarnessReport(report, rootDir, options) {
725
726
  warnings
726
727
  };
727
728
  }
728
- function validateDeterministicFloor(report, floor, check, warn) {
729
+ function validateDeterministicFloor(report, floor, check, warn, concern) {
729
730
  if (!floor)
730
731
  return;
731
732
  if (process.env.MODA_HARNESS_FLOOR_CHECK === "0")
732
733
  return;
734
+ const applyToolFloor = concern === undefined || concern === "tools";
735
+ const applyTopologyFloors = concern === undefined || concern === "topology";
733
736
  const graph = isRecord(report?.graph) ? report.graph : {};
734
737
  const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord(artifact) && artifact.type === "tool").length : 0;
735
738
  const providerCount = safeArrayLength(graph.modelProviders);
736
739
  const runtimeAgentCount = safeArrayLength(graph.runtimeAgents);
737
- if (typeof floor.toolCandidates === "number" && floor.toolCandidates >= 5) {
740
+ if (applyToolFloor && typeof floor.toolCandidates === "number" && floor.toolCandidates >= 5) {
738
741
  const minimumTools = Math.ceil(0.5 * floor.toolCandidates);
739
742
  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
743
  }
741
744
  const providerPackages = Array.isArray(floor.providerPackages) ? floor.providerPackages.filter((name) => typeof name === "string" && name.length > 0) : [];
742
- if (providerPackages.length > 0) {
745
+ if (applyTopologyFloors && providerPackages.length > 0) {
743
746
  check("floor_providers", providerCount > 0, `Manifest dependencies include model-provider package(s) ${providerPackages.join(", ")}, but the report graph.modelProviders is empty.`);
744
747
  }
745
- if (typeof floor.llmCallSites === "number" && floor.llmCallSites > 0 && runtimeAgentCount === 0) {
748
+ if (applyTopologyFloors && typeof floor.llmCallSites === "number" && floor.llmCallSites > 0 && runtimeAgentCount === 0) {
746
749
  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
750
  }
748
751
  }
@@ -1651,7 +1654,7 @@ function citationExcerptMatches(expected, actualLine) {
1651
1654
  return expectedNormalized.includes(actualNormalized) || actualNormalized.includes(expectedNormalized);
1652
1655
  }
1653
1656
  function normalizeComparableText(value) {
1654
- return value.replace(/\s+/g, " ").trim().slice(0, 240);
1657
+ return value.replace(/\s+/g, " ").trim();
1655
1658
  }
1656
1659
  function validateCitation(citation, rootDir, errors, warnings, options) {
1657
1660
  if (citation.type === "command_output" || citation.type === "production_evidence") {
@@ -2973,7 +2976,6 @@ function assertHarnessGraphSyncSize(graph) {
2973
2976
  `));
2974
2977
  }
2975
2978
  }
2976
-
2977
2979
  // src/tui.ts
2978
2980
  var STATUS_LABELS = {
2979
2981
  pass: "ok",
@@ -3524,6 +3526,13 @@ async function runHarnessCommand(context) {
3524
3526
  writeHumanProgress(context, "moda harness analyze is disabled: connect the Moda GitHub App (Settings → Integrations) for automatic analysis, or pass --experimental to run the CLI analyze.");
3525
3527
  return { exitCode: 1 };
3526
3528
  }
3529
+ const concern = resolveHarnessAnalyzeConcern(context.flags, context.env);
3530
+ if (concern) {
3531
+ const unsupported = context.flags["github-actions"] === "true" ? "--github-actions" : isRemoteHarnessAnalyze(context) ? "hosted (--remote) analysis" : await resolveHarnessAnalystAdapter(context) === "local-scan" ? "--analyst=local-scan" : "";
3532
+ if (unsupported) {
3533
+ throw new CliInputError(`--concern=${concern} is not supported by ${unsupported}.`, "Concern scoping narrows the analyst prompt and gates deterministic seeds, promotion, and floors, which only the local coding-agent analyst does today. Drop --concern, or run with --analyst=claude|codex|cursor.");
3534
+ }
3535
+ }
3527
3536
  await approveScanIfNeeded(scanPlan, {
3528
3537
  context,
3529
3538
  message: "Run Moda Analyst to investigate this project and produce a cited harness report?",
@@ -3537,7 +3546,7 @@ async function runHarnessCommand(context) {
3537
3546
  elapsed_ms: Date.now() - context.startedAt
3538
3547
  });
3539
3548
  if (context.flags["github-actions"] === "true") {
3540
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-v98snak8.js");
3549
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-kbw4z7q4.js");
3541
3550
  const result = await runGithubActionsAnalyze(rootDir, {
3542
3551
  writeReport: (report2) => {
3543
3552
  const normalized = normalizeHarnessReport(report2);
@@ -3565,7 +3574,8 @@ async function runHarnessCommand(context) {
3565
3574
  rootDir,
3566
3575
  scanPlan,
3567
3576
  tenant,
3568
- adapter: analyst
3577
+ adapter: analyst,
3578
+ concern
3569
3579
  });
3570
3580
  }
3571
3581
  context.output.writeEvent({
@@ -3838,7 +3848,8 @@ async function runExternalHarnessAnalyst(options) {
3838
3848
  const prompt = renderExternalHarnessAnalystPrompt({
3839
3849
  adapter: options.adapter,
3840
3850
  rootDir: options.rootDir,
3841
- tenantId: options.tenant?.tenantId
3851
+ tenantId: options.tenant?.tenantId,
3852
+ concern: options.concern
3842
3853
  });
3843
3854
  const streamAnalystProgress = options.context.outputMode === "agent-stream";
3844
3855
  const invocation = resolveExternalHarnessAnalystInvocation({
@@ -3924,7 +3935,7 @@ async function runExternalHarnessAnalyst(options) {
3924
3935
  ${result.stderr}`, 1200);
3925
3936
  throw new CliInputError(`Harness analyst ${options.adapter} exited with status ${result.status ?? "unknown"}.`, output || "Rerun with the selected coding agent CLI fixed, or choose another analyst with `--analyst=claude|codex|cursor`.");
3926
3937
  }
3927
- const report = readExternalAnalystReport(options.rootDir, result, options.adapter);
3938
+ const report = readExternalAnalystReport(options.rootDir, result, options.adapter, options.concern);
3928
3939
  analystTui?.setSummary({
3929
3940
  runtimeAgentCount: report.graph.runtimeAgents.length,
3930
3941
  agentFamilyCount: report.graph.agentFamilies.length,
@@ -3943,7 +3954,8 @@ ${result.stderr}`, 1200);
3943
3954
  });
3944
3955
  analystTui?.setValidation("running");
3945
3956
  const validation = validateHarnessReport(report, options.rootDir, {
3946
- deterministicFloor: computeDeterministicFloor(options.rootDir)
3957
+ deterministicFloor: computeDeterministicFloor(options.rootDir),
3958
+ concern: options.concern
3947
3959
  });
3948
3960
  analystTui?.setValidation(validation.status === "pass" ? "pass" : "fail");
3949
3961
  if (!analystTui)
@@ -3993,10 +4005,10 @@ function buildExternalAnalystEnv(env) {
3993
4005
  }
3994
4006
  return next;
3995
4007
  }
3996
- function readExternalAnalystReport(rootDir, result, adapter) {
4008
+ function readExternalAnalystReport(rootDir, result, adapter, concern) {
3997
4009
  const reportPath = resolve5(rootDir, HARNESS_REPORT_PATH);
3998
4010
  if (existsSync3(reportPath))
3999
- return repairExternalAnalystReport(readHarnessReport(reportPath), rootDir);
4011
+ return repairExternalAnalystReport(readHarnessReport(reportPath), rootDir, { concern });
4000
4012
  const output = [
4001
4013
  result.stdout,
4002
4014
  result.stderr,
@@ -4004,7 +4016,7 @@ function readExternalAnalystReport(rootDir, result, adapter) {
4004
4016
  extractAgentTextFromOutput(result.stderr)
4005
4017
  ].filter(Boolean).join(`
4006
4018
  `);
4007
- const parsed = extractHarnessReportFromText(output, rootDir);
4019
+ const parsed = extractHarnessReportFromText(output, rootDir, concern);
4008
4020
  if (parsed)
4009
4021
  return parsed;
4010
4022
  let debugHint = "";
@@ -4028,11 +4040,11 @@ function readExternalAnalystReport(rootDir, result, adapter) {
4028
4040
  ].filter(Boolean).join(`
4029
4041
  `));
4030
4042
  }
4031
- function extractHarnessReportFromText(output, rootDir) {
4043
+ function extractHarnessReportFromText(output, rootDir, concern) {
4032
4044
  for (const candidate of reportJsonCandidates(output)) {
4033
4045
  const parsed = parseHarnessReportCandidate(candidate);
4034
4046
  if (parsed)
4035
- return repairExternalAnalystReport(parsed, rootDir);
4047
+ return repairExternalAnalystReport(parsed, rootDir, { concern });
4036
4048
  }
4037
4049
  return;
4038
4050
  }
@@ -4066,13 +4078,32 @@ function parseHarnessReportCandidate(candidate) {
4066
4078
  }
4067
4079
  return;
4068
4080
  }
4069
- function repairExternalAnalystReport(report, rootDir) {
4081
+ function repairExternalAnalystReport(report, rootDir, options = {}) {
4070
4082
  const normalized = normalizeHarnessReport(report);
4071
4083
  repairCitationLines(normalized, rootDir);
4072
- mergeDeterministicArtifacts(normalized.graph, rootDir);
4084
+ mergeDeterministicArtifacts(normalized.graph, rootDir, { concern: options.concern });
4073
4085
  hydratePromptAndToolBodies(normalized.graph, rootDir);
4074
4086
  deriveRelationshipsFromGraph(normalized.graph);
4075
4087
  repairReportSummaryCounts(normalized);
4088
+ if (options.concern) {
4089
+ normalized.source = {
4090
+ ...normalized.source ?? {
4091
+ graphSchemaVersion: normalized.graph?.schemaVersion ?? SCHEMA_VERSION,
4092
+ graphHash: "",
4093
+ readPlan: normalized.graph?.scan?.readPlan ?? {
4094
+ scope: "broad",
4095
+ rootPaths: ["."],
4096
+ willRead: [],
4097
+ willSkip: [],
4098
+ skipsSecretFiles: true,
4099
+ followsSymlinks: false,
4100
+ uploadsSourceByDefault: false
4101
+ },
4102
+ deterministicInventory: false
4103
+ },
4104
+ concern: options.concern
4105
+ };
4106
+ }
4076
4107
  if (normalized.source?.graphHash) {
4077
4108
  normalized.source.graphHash = hashStableJson(graphForReport(normalized.graph));
4078
4109
  }
@@ -4114,9 +4145,18 @@ function deriveRelationshipsFromGraph(graph) {
4114
4145
  }
4115
4146
  return { added };
4116
4147
  }
4117
- function mergeDeterministicArtifacts(graph, rootDir) {
4148
+ var CONCERN_PROMOTED_ARTIFACT_TYPES = {
4149
+ topology: new Set,
4150
+ tools: new Set(["tool"]),
4151
+ prompts: new Set(["prompt"]),
4152
+ evals: new Set(["model_config"])
4153
+ };
4154
+ function mergeDeterministicArtifacts(graph, rootDir, options = {}) {
4118
4155
  if (process.env.MODA_HARNESS_DETERMINISTIC_MERGE === "0")
4119
4156
  return { added: 0 };
4157
+ if (options.concern === "topology")
4158
+ return { added: 0 };
4159
+ const allowedTypes = options.concern ? CONCERN_PROMOTED_ARTIFACT_TYPES[options.concern] : undefined;
4120
4160
  let promoted;
4121
4161
  try {
4122
4162
  promoted = promoteHarnessCandidates(detectHarnessCandidates(rootDir), rootDir);
@@ -4125,6 +4165,8 @@ function mergeDeterministicArtifacts(graph, rootDir) {
4125
4165
  }
4126
4166
  let added = 0;
4127
4167
  for (const artifact of promoted) {
4168
+ if (allowedTypes && !allowedTypes.has(artifact.type))
4169
+ continue;
4128
4170
  if (graphCoversPromotedArtifact(graph, artifact))
4129
4171
  continue;
4130
4172
  graph.artifacts.push(artifact);
@@ -4195,9 +4237,22 @@ function repairCitationLines(report, rootDir) {
4195
4237
  const currentLine = typeof citation.line === "number" ? lines[citation.line - 1] : undefined;
4196
4238
  if (currentLine && citationLineMatches(currentLine, excerpt))
4197
4239
  continue;
4198
- const matchedIndex = lines.findIndex((line) => citationLineMatches(line, excerpt));
4199
- if (matchedIndex >= 0)
4200
- citation.line = matchedIndex + 1;
4240
+ const needle = normalizeComparableLine(excerpt);
4241
+ if (!needle)
4242
+ continue;
4243
+ const sourceLines = lines.map((line, index) => ({ text: normalizeComparableLine(line), index })).filter((line) => line.text.length > 0);
4244
+ const source = sourceLines.map((line) => line.text).join(" ");
4245
+ const match = source.indexOf(needle);
4246
+ if (match < 0 || source.indexOf(needle, match + 1) >= 0)
4247
+ continue;
4248
+ let offset = 0;
4249
+ for (const line of sourceLines) {
4250
+ if (match >= offset && match < offset + line.text.length) {
4251
+ citation.line = line.index + 1;
4252
+ break;
4253
+ }
4254
+ offset += line.text.length + 1;
4255
+ }
4201
4256
  }
4202
4257
  }
4203
4258
  function citationLineMatches(line, excerpt) {
@@ -4661,6 +4716,17 @@ function isDeterministicLocalAnalyst(raw) {
4661
4716
  const value = raw?.trim().toLowerCase();
4662
4717
  return value === "local-scan" || value === "local" || value === "scripted" || value === "moda";
4663
4718
  }
4719
+ function resolveHarnessAnalyzeConcern(flags, env = process.env) {
4720
+ const flagValue = flags.concern && flags.concern !== "true" ? flags.concern : undefined;
4721
+ const raw = flagValue ?? env.MODA_HARNESS_CONCERN;
4722
+ const value = raw?.trim().toLowerCase();
4723
+ if (!value)
4724
+ return;
4725
+ if (HARNESS_ANALYZE_CONCERNS.includes(value)) {
4726
+ return value;
4727
+ }
4728
+ throw new CliInputError(`Unknown harness analyze concern '${raw}'. Valid concerns: ${HARNESS_ANALYZE_CONCERNS.join("|")}.`, `Use --concern=${HARNESS_ANALYZE_CONCERNS.join("|")} (or MODA_HARNESS_CONCERN; the flag wins).`);
4729
+ }
4664
4730
  function normalizeAnalystAdapter(raw) {
4665
4731
  const value = (raw && raw !== "true" ? raw : "auto").trim().toLowerCase();
4666
4732
  if (value === "auto")
@@ -4692,7 +4758,7 @@ function resolveExternalHarnessAnalystInvocation(options) {
4692
4758
  if (options.adapter === "claude") {
4693
4759
  const extraArgs = externalAnalystExtraArgs(options.adapter);
4694
4760
  const model = resolveAnalystModel(options.adapter, options.flags);
4695
- const permission = mode === "write" ? ["--permission-mode", "acceptEdits", "--allowedTools", "Read,Grep,Glob,Edit,Write,Bash"] : ["--allowedTools", `Read,Grep,Glob,Write(${HARNESS_REPORT_PATH}),Bash(pwd),Bash(ls *),Bash(find *),Bash(rg *),Bash(grep *),Bash(cat *),Bash(git status *),Bash(git ls-files *),Bash(git grep *)`];
4761
+ const permission = mode === "write" ? ["--permission-mode", "acceptEdits", "--allowedTools", "Read,Grep,Glob,Edit,Write,Bash"] : ["--allowedTools", `Read,Grep,Glob,Edit(${HARNESS_REPORT_PATH}),Bash(pwd),Bash(ls *),Bash(find *),Bash(rg *),Bash(grep *),Bash(cat *),Bash(git status *),Bash(git ls-files *),Bash(git grep *)`];
4696
4762
  return {
4697
4763
  adapter: options.adapter,
4698
4764
  command,
@@ -4798,9 +4864,100 @@ function resolveAnalystModel(adapter, flags) {
4798
4864
  return raw;
4799
4865
  return adapter === "claude" ? DEFAULT_CLAUDE_ANALYST_MODEL : undefined;
4800
4866
  }
4867
+ var CONCERN_AGENT_STUB_LINE = 'Runtime agents may be represented ONLY as bare low-confidence stubs when linkage needs a target: id, name, kind:"runtime_agent", root, confidence, evidence — nothing more. Do not investigate agent topology beyond that; the topology concern pass owns it.';
4868
+ var CONCERN_LOCATION_PIN_LINE = "Every artifact you author MUST pin an exact source location so downstream optimization can retrieve the item text: carry a numeric `line` on at least one evidence entry (evidence: [{ path, line, reason }]) at the definition line. The only exception is an artifact whose entire dedicated file is the text — set its sourcePath and cite that file, no line required. When a single file holds multiple inline definitions, give each artifact its own distinct line.";
4869
+ var CONCERN_NO_TRANSCRIPTION_LINE = "Do NOT transcribe prompt/tool text into `body` when it exists verbatim in the repo — pin its exact location instead and Moda hydrates `body` locally after validation. Set `body` inline ONLY for content that cannot be recovered from one pinned location (e.g. fragments assembled at runtime). Redact any secret literals.";
4870
+ var CONCERN_CAPS_LINE = "- Keep going until every in-concern item is represented as an artifact. Respect the hard caps (<=250 claims, <=400 citations): artifacts are uncapped, so keep enumerating them as artifacts and reserve claims/citations for the most important ones.";
4871
+ var CONCERN_PROMPT_SPECS = {
4872
+ topology: {
4873
+ mission: "Your single concern for this pass is TOPOLOGY: the agents and the infrastructure they run on. Other concern passes exhaustively enumerate tools, prompts, and evals — you must not.",
4874
+ taskLines: [
4875
+ "1. Map the harness topology: runtime agents, agent families, channel adapters, identity namespaces, environments, agent frameworks, model providers, deployments, the relationships among them, and open questions.",
4876
+ "2. Do NOT exhaustively enumerate tools, prompts, guardrails, or evals as artifacts — separate concern passes produce those inventories. Keep graph.artifacts minimal: entrypoint artifacts only, plus agent artifactIds references you discover naturally along the way."
4877
+ ],
4878
+ protocolLines: [
4879
+ "- Focus EXCLUSIVELY on topology: runtime agents, agent families, channel adapters, identity namespaces, environments, agent frameworks, model providers, deployments, and how they relate.",
4880
+ "- Do NOT enumerate tools, prompts, guardrails, or evals as artifacts. Keep graph.artifacts minimal: entrypoints only.",
4881
+ "- For every runtime agent, read and cite at least one concrete entrypoint or orchestration line, and populate every linkage field (entrypointIds, channelAdapterIds, identityNamespaceIds, deploymentIds, frameworkIds, modelProviderIds).",
4882
+ "- 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."
4883
+ ],
4884
+ contractLines: [
4885
+ 'Runtime agents must include id, name, kind:"runtime_agent", root, entrypointIds, channelAdapterIds, artifactIds, identityNamespaceIds, deploymentIds, frameworkIds, modelProviderIds, confidence, evidence. Agent linkage fields are the product of this pass — empty linkage means a disconnected map.',
4886
+ "Agent families must include id, name, runtimeAgentIds, sharedArtifactIds, confidence, evidence.",
4887
+ "Frameworks/providers must include packageNames and runtimeAgentIds arrays; providers also include envVars and modelHints arrays.",
4888
+ "Keep graph.artifacts minimal (entrypoints only); do not enumerate tools, prompts, guardrails, or evals."
4889
+ ],
4890
+ largeRepoLine: "If the repository is very large, model runtime agents representatively rather than exhaustively, but never drop channel adapters, providers, frameworks, or deployments you have evidence for."
4891
+ },
4892
+ tools: {
4893
+ mission: "Your single concern for this pass is TOOLS: the exhaustive tool inventory. Other concern passes cover agents/topology, prompts, and evals — you must not enumerate those.",
4894
+ taskLines: [
4895
+ "1. Exhaustively enumerate EVERY tool as its own `tool` graph artifact — the complete inventory, not a sample. This report feeds tool optimization, so a missing tool is a real defect.",
4896
+ "2. Do NOT enumerate prompts, guardrails, or evals, and do not map agent topology beyond bare stubs needed for tool linkage."
4897
+ ],
4898
+ protocolLines: [
4899
+ "- Tools: emit a `tool` artifact for EVERY tool definition — each `@function_tool` / `function_tool(...)`-decorated function, every hosted/first-party tool class (e.g. WebSearchTool, FileSearchTool, ComputerTool, CodeInterpreterTool, HostedMCPTool, ImageGenerationTool, LocalShellTool), and every `tool({...})` / `createTool(...)` / `new *Tool(...)` factory. Name each artifact after the tool/function and cite its definition line.",
4900
+ "- Populate usedByAgentIds on every tool artifact whenever the using agent is determinable (and ownedByAgentId when a single agent owns it).",
4901
+ `- ${CONCERN_AGENT_STUB_LINE}`,
4902
+ "- Do NOT emit prompt, guardrail, eval, memory, or retrieval_index artifacts — other concern passes own those.",
4903
+ CONCERN_CAPS_LINE
4904
+ ],
4905
+ contractLines: [
4906
+ 'Artifacts must include id, type:"tool", name, scope, usedByAgentIds, confidence, evidence, and optional sourcePath/contentHash/ownedByAgentId. Emit one artifact PER tool — enumerate them all.',
4907
+ CONCERN_NO_TRANSCRIPTION_LINE,
4908
+ CONCERN_LOCATION_PIN_LINE,
4909
+ CONCERN_AGENT_STUB_LINE
4910
+ ],
4911
+ largeRepoLine: "If the repository is very large, keep enumerating tools as artifacts (uncapped) — do not drop tools to save space."
4912
+ },
4913
+ prompts: {
4914
+ mission: "Your single concern for this pass is PROMPTS: the exhaustive prompt inventory. Other concern passes cover agents/topology, tools, and evals — you must not enumerate those.",
4915
+ taskLines: [
4916
+ "1. Exhaustively enumerate EVERY distinct prompt as its own `prompt` graph artifact — dedicated prompt files AND inline system prompts, the complete inventory, not a sample. This report feeds prompt optimization, so a missing prompt is a real defect.",
4917
+ "2. Do NOT enumerate tools, guardrails, or evals, and do not map agent topology beyond bare stubs needed for prompt linkage."
4918
+ ],
4919
+ protocolLines: [
4920
+ "- Prompts: emit a `prompt` artifact for EVERY distinct prompt — dedicated prompt files AND inline system prompts (`instructions=`, `system=`, `SYSTEM_PROMPT`, prompt-template strings), one per agent/definition, each with its exact file+line location so the prompt text can be retrieved and optimized later.",
4921
+ "- Populate usedByAgentIds on every prompt artifact whenever the using agent is determinable (and ownedByAgentId when a single agent owns it).",
4922
+ `- ${CONCERN_AGENT_STUB_LINE}`,
4923
+ "- Do NOT emit tool, guardrail, eval, memory, or retrieval_index artifacts — other concern passes own those.",
4924
+ CONCERN_CAPS_LINE
4925
+ ],
4926
+ contractLines: [
4927
+ 'Artifacts must include id, type:"prompt", name, scope, usedByAgentIds, confidence, evidence, and optional sourcePath/contentHash/ownedByAgentId. Emit one artifact PER prompt (including each inline system prompt) — enumerate them all.',
4928
+ CONCERN_NO_TRANSCRIPTION_LINE,
4929
+ CONCERN_LOCATION_PIN_LINE,
4930
+ CONCERN_AGENT_STUB_LINE
4931
+ ],
4932
+ largeRepoLine: "If the repository is very large, keep enumerating prompts as artifacts (uncapped) — do not drop prompts to save space."
4933
+ },
4934
+ evals: {
4935
+ mission: "Your single concern for this pass is EVALS & SAFETY CONFIG: evals, guardrails, model configs, and memory/retrieval stores. Other concern passes cover agents/topology, tools, and prompts — you must not enumerate those.",
4936
+ taskLines: [
4937
+ '1. Exhaustively enumerate EVERY eval (type "eval"), guardrail (type "guardrail"), model configuration (type "model_config"), memory/session store (type "memory"), and retrieval index (type "retrieval_index") as its own graph artifact — the complete inventory, not a sample.',
4938
+ "2. Do NOT enumerate tools or prompts, and do not map agent topology beyond bare stubs needed for linkage."
4939
+ ],
4940
+ protocolLines: [
4941
+ '- Emit artifacts for evals/tests-of-agents (type "eval"), guardrails (type "guardrail"), model configuration (type "model_config"), memory/session stores (type "memory"), and retrieval indexes (type "retrieval_index").',
4942
+ "- Short bodies are welcome here: `guardrail` (the rule), `eval` (what it asserts), `model_config` (the config values). Redact any secret literals.",
4943
+ "- Populate usedByAgentIds on every artifact whenever the using agent is determinable.",
4944
+ `- ${CONCERN_AGENT_STUB_LINE}`,
4945
+ "- Do NOT emit tool or prompt artifacts — other concern passes own those.",
4946
+ CONCERN_CAPS_LINE
4947
+ ],
4948
+ contractLines: [
4949
+ "Artifacts must include id, type, name, scope, usedByAgentIds, confidence, evidence, and optional sourcePath/contentHash/ownedByAgentId; artifact types for this pass are eval, guardrail, model_config, memory, retrieval_index. Emit one artifact PER item — enumerate them all.",
4950
+ "Short bodies are welcome: `guardrail` (the rule), `eval` (what it asserts), `model_config` (the config values). Redact any secret literals.",
4951
+ CONCERN_AGENT_STUB_LINE
4952
+ ],
4953
+ largeRepoLine: "If the repository is very large, keep enumerating evals, guardrails, and model configs as artifacts (uncapped) — do not drop them to save space."
4954
+ }
4955
+ };
4801
4956
  function renderExternalHarnessAnalystPrompt(options) {
4957
+ const spec = options.concern ? CONCERN_PROMPT_SPECS[options.concern] : undefined;
4802
4958
  return [
4803
4959
  "You are Moda Analyst, investigating an agent harness for Moda CLI onboarding.",
4960
+ spec?.mission ?? "",
4804
4961
  "",
4805
4962
  "Do real repository investigation. Do not call `moda harness scan` or `moda harness analyze` yourself. When Moda provides deterministic scanner seeds below, treat them as navigation candidates to verify against the real files — never as evidence or as a complete inventory.",
4806
4963
  "Only inspect the Repository root named below and files/directories under it. Do not inspect parent directories, sibling projects, the Moda CLI implementation, or this monorepo unless the Repository root itself is that directory.",
@@ -4810,13 +4967,15 @@ function renderExternalHarnessAnalystPrompt(options) {
4810
4967
  `The ONLY path you may write is ${HARNESS_REPORT_PATH}. Write the final report JSON there with the Write tool as soon as it is complete — the file is the durable deliverable and survives a dropped connection, unlike a long printed message. If (and only if) the write fails, print the report JSON between the sentinel markers instead.`,
4811
4968
  "",
4812
4969
  "Your task:",
4813
- renderExternalAnalystProtocol(),
4814
- "1. Identify the runtime agent harnesses, agent families, channel adapters, identity mappings, model providers, frameworks, deployments, and open questions.",
4815
- "2. Exhaustively enumerate EVERY prompt, tool, guardrail, and eval as its own graph artifact — the complete inventory, not a sample. This report feeds prompt optimization and tool optimization, so a missing tool or prompt is a real defect. Runtime agents may be modeled representatively on framework/example repos, but prompts and tools must be complete.",
4970
+ renderExternalAnalystProtocol(options.concern),
4971
+ ...spec ? spec.taskLines : [
4972
+ "1. Identify the runtime agent harnesses, agent families, channel adapters, identity mappings, model providers, frameworks, deployments, and open questions.",
4973
+ "2. Exhaustively enumerate EVERY prompt, tool, guardrail, and eval as its own graph artifact — the complete inventory, not a sample. This report feeds prompt optimization and tool optimization, so a missing tool or prompt is a real defect. Runtime agents may be modeled representatively on framework/example repos, but prompts and tools must be complete."
4974
+ ],
4816
4975
  "3. Cite every narrative claim with local file paths, exact line numbers, and excerpts. File citations without line numbers are invalid for agent-authored reports. Enumerated artifacts only need evidence:[{path, reason}], so completeness of the artifact inventory is not limited by the citation cap.",
4817
4976
  `4. After completing investigation, WRITE the report JSON to ${HARNESS_REPORT_PATH} with the Write tool, then print a one-line confirmation naming the file. Only if that write fails, print the report JSON between ${REPORT_JSON_BEGIN} and ${REPORT_JSON_END}. Moda CLI validates whichever it finds (the file wins).`,
4818
4977
  "Do not end with commentary about being read-only and do not summarize without delivering the report: either the written file or the sentinel-wrapped JSON must exist before you finish.",
4819
- "If the repository is very large, keep enumerating prompts/tools/guardrails/evals as artifacts (uncapped) and only summarize runtime AGENTS representatively — do not drop tools or prompts to save space.",
4978
+ spec?.largeRepoLine ?? "If the repository is very large, keep enumerating prompts/tools/guardrails/evals as artifacts (uncapped) and only summarize runtime AGENTS representatively — do not drop tools or prompts to save space.",
4820
4979
  "",
4821
4980
  "Final output format:",
4822
4981
  REPORT_JSON_BEGIN,
@@ -4830,7 +4989,7 @@ function renderExternalHarnessAnalystPrompt(options) {
4830
4989
  "- reportId, harnessId, createdAt",
4831
4990
  `- analyst: { "id": "moda-${options.adapter}-analyst", "adapter": "${options.adapter}", "mode": "agent", "version": "cli" }`,
4832
4991
  '- repo: { root: ".", name, packageManagers, languages }',
4833
- '- source: { graphSchemaVersion: "harness.v0.1", graphHash: "", readPlan, deterministicInventory: false }',
4992
+ options.concern ? `- source: { graphSchemaVersion: "harness.v0.1", graphHash: "", readPlan, deterministicInventory: false, concern: "${options.concern}" }` : '- source: { graphSchemaVersion: "harness.v0.1", graphHash: "", readPlan, deterministicInventory: false }',
4834
4993
  "- summary: numeric fields named runtimeAgentCount, agentFamilyCount, artifactCount, sharedArtifactCount, channelAdapterCount, identityNamespaceCount, environmentCount, agentFrameworkCount, modelProviderCount, deploymentCount, relationshipCount, unknownCount",
4835
4994
  "- graph: an exact sanitized harness.v0.1 graph with arrays for agentFamilies, runtimeAgents, developerAgentAdapters, channelAdapters, artifacts, identityNamespaces, environments, agentFrameworks, modelProviders, deployments, relationships, unknowns",
4836
4995
  "- claims: cited claims about graph items. Each claim must include kind, subjectId, title, summary, confidence, inference, and citationIds; kind should be one of runtime_agent, agent_family, developer_agent_adapter, artifact, channel_adapter, identity_namespace, environment, agent_framework, model_provider, deployment, relationship, unknown. subjectId should be the id of the runtime agent, agent family, developer adapter, artifact, provider, adapter, deployment, relationship, or unknown being claimed.",
@@ -4840,13 +4999,15 @@ function renderExternalHarnessAnalystPrompt(options) {
4840
4999
  "",
4841
5000
  'Use exact field names and numeric confidence values from 0 to 1. Do not write string confidence values like "high". Do not use confidence >= 0.90 unless the claim is direct evidence from the cited line(s).',
4842
5001
  "Each graph item with evidence must use evidence: [{ path, reason }], not citationIds/citations.",
4843
- 'Runtime agents must include id, name, kind:"runtime_agent", root, entrypointIds, channelAdapterIds, artifactIds, identityNamespaceIds, deploymentIds, frameworkIds, modelProviderIds, confidence, evidence.',
4844
- "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.",
4845
- "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.",
4846
- "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.",
4847
- "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.",
4848
- "Agent families must include id, name, runtimeAgentIds, sharedArtifactIds, confidence, evidence.",
4849
- "Frameworks/providers must include packageNames and runtimeAgentIds arrays; providers also include envVars and modelHints arrays.",
5002
+ ...spec ? spec.contractLines : [
5003
+ 'Runtime agents must include id, name, kind:"runtime_agent", root, entrypointIds, channelAdapterIds, artifactIds, identityNamespaceIds, deploymentIds, frameworkIds, modelProviderIds, confidence, evidence.',
5004
+ "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.",
5005
+ "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.",
5006
+ "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.",
5007
+ "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.",
5008
+ "Agent families must include id, name, runtimeAgentIds, sharedArtifactIds, confidence, evidence.",
5009
+ "Frameworks/providers must include packageNames and runtimeAgentIds arrays; providers also include envVars and modelHints arrays."
5010
+ ],
4850
5011
  "Unknowns in graph must include id, question, candidateIds, evidence. Report-level unknowns must include id, question, citationIds.",
4851
5012
  "Do not make every claim about the repo or harness id. Claims should name the specific graph item they support.",
4852
5013
  "",
@@ -4854,16 +5015,24 @@ function renderExternalHarnessAnalystPrompt(options) {
4854
5015
  options.tenantId ? `Tenant ID to include in graph.tenant: ${options.tenantId}` : "No tenant ID was provided for this analysis.",
4855
5016
  `Repository root: ${options.rootDir}`,
4856
5017
  renderAnalystPreflightSection(options.rootDir),
4857
- renderAnalystSeedSection(options.rootDir),
5018
+ renderAnalystSeedSection(options.rootDir, options.concern),
4858
5019
  "",
4859
5020
  "Be conservative. If something is unclear, put it in unknowns instead of inventing an agent."
4860
5021
  ].filter(Boolean).join(`
4861
5022
  `);
4862
5023
  }
4863
- function renderExternalAnalystProtocol() {
5024
+ function renderExternalAnalystProtocol(concern) {
4864
5025
  const protocol = process.env.MODA_HARNESS_ANALYST_PROTOCOL?.trim().toLowerCase();
4865
5026
  if (protocol === "classic" || protocol === "none" || protocol === "0")
4866
5027
  return "";
5028
+ if (concern) {
5029
+ return [
5030
+ "Focused investigation protocol:",
5031
+ "- First build a quick repo map with read-only commands such as pwd, git ls-files, rg, and targeted manifest/doc reads.",
5032
+ ...CONCERN_PROMPT_SPECS[concern].protocolLines
5033
+ ].join(`
5034
+ `);
5035
+ }
4867
5036
  return [
4868
5037
  "Focused investigation protocol:",
4869
5038
  "- First build a quick repo map with read-only commands such as pwd, git ls-files, rg, and targeted manifest/doc reads.",
@@ -4912,23 +5081,31 @@ function renderPreflightGroup(label, paths) {
4912
5081
  return [`${label}:`, ...paths.map((path) => `- ${path}`)];
4913
5082
  }
4914
5083
  var MAX_SEED_ARTIFACT_LINES = 150;
4915
- function renderAnalystSeedSection(rootDir) {
5084
+ function renderAnalystSeedSection(rootDir, concern) {
4916
5085
  if (process.env.MODA_HARNESS_ANALYST_SEEDS === "0")
4917
5086
  return "";
4918
5087
  const sections = [];
5088
+ if (concern === undefined || concern === "topology") {
5089
+ try {
5090
+ const lines = renderAnalystSeedLines(scanHarness({ rootDir, dryRun: true, pathWasExplicit: true }));
5091
+ if (lines)
5092
+ sections.push(lines);
5093
+ } catch {}
5094
+ }
4919
5095
  try {
4920
- const lines = renderAnalystSeedLines(scanHarness({ rootDir, dryRun: true, pathWasExplicit: true }));
4921
- if (lines)
4922
- sections.push(lines);
4923
- } catch {}
4924
- try {
4925
- const lines = renderAnalystCandidateLines(detectHarnessCandidates(rootDir));
5096
+ const lines = renderAnalystCandidateLines(detectHarnessCandidates(rootDir), concern);
4926
5097
  if (lines)
4927
5098
  sections.push(lines);
4928
5099
  } catch {}
4929
5100
  return sections.join(`
4930
5101
  `);
4931
5102
  }
5103
+ var CONCERN_CANDIDATE_KINDS = {
5104
+ topology: ["llm_call_site", "framework_construct"],
5105
+ tools: ["tool_definition", "tool_schema"],
5106
+ prompts: ["prompt_file", "prompt_literal", "message_construction"],
5107
+ evals: ["model_config"]
5108
+ };
4932
5109
  var MAX_SEED_CANDIDATES_PER_KIND = 40;
4933
5110
  var MAX_SEED_READ_PLAN_FILES = 25;
4934
5111
  var SEED_CANDIDATE_KIND_LABELS = {
@@ -4941,11 +5118,13 @@ var SEED_CANDIDATE_KIND_LABELS = {
4941
5118
  prompt_literal: "Inline prompt literals",
4942
5119
  model_config: "Model configuration"
4943
5120
  };
4944
- function renderAnalystCandidateLines(report) {
4945
- if (report.candidates.length === 0)
5121
+ function renderAnalystCandidateLines(report, concern) {
5122
+ const allowedKinds = concern ? new Set(CONCERN_CANDIDATE_KINDS[concern]) : undefined;
5123
+ const candidates = allowedKinds ? report.candidates.filter((candidate) => allowedKinds.has(candidate.kind)) : report.candidates;
5124
+ if (candidates.length === 0)
4946
5125
  return "";
4947
5126
  const byKind = new Map;
4948
- for (const candidate of report.candidates) {
5127
+ for (const candidate of candidates) {
4949
5128
  const bucket = byKind.get(candidate.kind);
4950
5129
  if (bucket)
4951
5130
  bucket.push(candidate);
@@ -4954,9 +5133,12 @@ function renderAnalystCandidateLines(report) {
4954
5133
  }
4955
5134
  const lines = [
4956
5135
  "",
4957
- `Deterministic candidate inventory (${report.candidates.length} candidates across ${report.stats.filesWithCandidates} files, scanned in ${report.stats.durationMs}ms):`,
4958
- "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."
5136
+ concern ? `Deterministic candidate inventory for the ${concern} concern (${candidates.length} candidates, scanned in ${report.stats.durationMs}ms):` : `Deterministic candidate inventory (${candidates.length} candidates across ${report.stats.filesWithCandidates} files, scanned in ${report.stats.durationMs}ms):`,
5137
+ concern ? "Each entry is path:line [detector] excerpt. Candidates are pattern matches — verify each against the real file, drop false positives (list refuted ones in unknowns), and hunt for in-concern items the patterns cannot see. Do not cite this list as evidence; cite the files." : "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."
4959
5138
  ];
5139
+ if (concern === "evals") {
5140
+ lines.push("Note: evals, guardrails, and memory/retrieval stores have no deterministic detector — only model_config candidates are seeded. Discover the rest by investigation.");
5141
+ }
4960
5142
  for (const [kind, label] of Object.entries(SEED_CANDIDATE_KIND_LABELS)) {
4961
5143
  const bucket = byKind.get(kind);
4962
5144
  if (!bucket || bucket.length === 0)