@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.
- package/dist/{cli-793dhrgb.js → cli-5e0rd8mf.js} +483 -36
- package/dist/{cli-n5qz81m0.js → cli-b2ktsnmh.js} +1 -1
- package/dist/cli.js +3 -3
- package/dist/{harness-github-actions-wd1ahhxz.js → harness-github-actions-dmy8fz54.js} +1 -1
- package/dist/{index-djg29jhz.js → index-6twsawta.js} +2 -2
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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|
|
|
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 =
|
|
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-
|
|
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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4182
|
+
const candidatePath = resolve5(rootDir, citation.path);
|
|
3896
4183
|
if (!existsSync3(candidatePath))
|
|
3897
4184
|
continue;
|
|
3898
4185
|
let text;
|
|
3899
4186
|
try {
|
|
3900
|
-
text =
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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,
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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 =
|
|
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
|
|
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(
|
|
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",
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
runPromptsCommand,
|
|
9
9
|
runSkillsCommand,
|
|
10
10
|
runStatusCommand
|
|
11
|
-
} from "./cli-
|
|
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-
|
|
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-
|
|
6499
|
+
const { runInit } = await import("./index-6twsawta.js");
|
|
6500
6500
|
if (context.outputMode === "agent-stream") {
|
|
6501
6501
|
context.output.writeEvent({
|
|
6502
6502
|
event: "started",
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
runPromptSync,
|
|
10
10
|
runSkillSync,
|
|
11
11
|
upsertSkillManifestRecord
|
|
12
|
-
} from "./cli-
|
|
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-
|
|
30
|
+
} from "./cli-5e0rd8mf.js";
|
|
31
31
|
import {
|
|
32
32
|
selectTenantAndCreateKey
|
|
33
33
|
} from "./cli-pq6rte0w.js";
|
package/package.json
CHANGED