@orangepro/orangepro-mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +195 -251
- package/dist/local/analyze/analyzer.js +46 -6
- package/dist/local/analyze/treeSitter/engine.js +172 -17
- package/dist/local/autoProve.js +402 -28
- package/dist/local/cli.js +93 -10
- package/dist/local/cliArgs.js +1 -0
- package/dist/local/generate/runHints.js +9 -4
- package/dist/local/graph/factories.js +5 -2
- package/dist/local/ledger.js +1 -1
- package/dist/local/mcp.js +26 -13
- package/dist/local/operations.js +396 -52
- package/dist/local/pack/coverageReport.js +3 -3
- package/dist/local/proofDoctor.js +312 -0
- package/dist/local/rtm.js +40 -10
- package/dist/local/viz/behaviorReportData.js +79 -7
- package/dist/local/viz/behaviorReportHtml.js +526 -615
- package/dist/local/viz/html.js +1 -1
- package/docs/agent-workflow.md +10 -38
- package/docs/agents/claude-code.md +3 -9
- package/docs/agents/codex.md +3 -20
- package/docs/agents/cursor.md +1 -1
- package/docs/agents/opencode.md +1 -1
- package/docs/agents/vscode.md +1 -1
- package/docs/local-proof-kit.md +52 -19
- package/package.json +39 -6
- package/scripts/spikes/go-dynamic-proof-spike.mjs +637 -0
- package/scripts/spikes/go-mutate.go +182 -0
- package/scripts/spikes/java-dynamic-proof-spike.mjs +571 -0
- package/scripts/spikes/java-mutate.mjs +264 -0
- package/scripts/spikes/python-dynamic-proof-spike.mjs +244 -0
- package/scripts/spikes/python-mutate.py +89 -0
package/dist/local/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs, collectSetupCommands } from "./cliArgs.js";
|
|
3
|
-
import { opAnalyze, opAiFlows, opAiLinks, opChanged, opCompare, opDoctor, opDynamicProof, opExplain, opGaps, opGenerate, opBehaviorCoverageHtml, opCoverageReport, opInit, opProveLoop, opRuntimeCoverage, opScore, opRecordRun, opRtm, opStats, opStatus, opUpdate, opSetModelDefault, opStart, getModelDefault, resolveDiffTargets, resolvePrCheckout, writeCompareReport } from "./operations.js";
|
|
3
|
+
import { opAnalyze, opAiFlows, opAiLinks, opChanged, opCompare, opDoctor, opProofDoctor, opDynamicProof, opExplain, opGaps, opGenerate, opBehaviorCoverageHtml, opCoverageReport, opInit, opProveLoop, opRuntimeCoverage, opScore, opRecordRun, opRtm, opStats, opStatus, opUpdate, opSetModelDefault, opStart, getModelDefault, resolveDiffTargets, resolvePrCheckout, writeCompareReport } from "./operations.js";
|
|
4
4
|
import { dominantBlockReason } from "./viz/behaviorReportData.js";
|
|
5
|
+
import { autoProve, isRoastSurvivor } from "./autoProve.js";
|
|
5
6
|
import { opRecipeDbSqljs } from "./recipe/dbSqljs.js";
|
|
6
7
|
import { runExportCli } from "./exportCli.js";
|
|
7
8
|
import { startLocalMcpServer } from "./mcp.js";
|
|
@@ -75,7 +76,8 @@ const HELP = `opro — OrangePro (local-first, BYOK, metadata-only artifacts)
|
|
|
75
76
|
|
|
76
77
|
Usage:
|
|
77
78
|
opro # one-command start: analyze, optional AI links + flows, report, RTM, agent handoff
|
|
78
|
-
opro start [path] [--base <ref>] [--no-ai] [--no-ai-flows] [--generate-coverage] [--json]
|
|
79
|
+
opro start [path] [--base <ref>] [--no-ai] [--no-ai-flows] [--generate-coverage] [--prompt-version v5] [--json]
|
|
80
|
+
opro roast [path] [--limit 5] [--json] # keyless: find passing tests whose targeted mutant still survives
|
|
79
81
|
opro init
|
|
80
82
|
opro setup # interactive: choose a default model provider + model (saved locally)
|
|
81
83
|
opro analyze [path] [--paths a.csv,b.md] [--include-markdown true|false] [--generate-coverage] [--coverage-timeout-ms 120000] [--ai-flows] [--no-coverage-html] [--json]
|
|
@@ -84,7 +86,7 @@ Usage:
|
|
|
84
86
|
opro coverage [path] [--generate] [--timeout-ms 120000] [--json]
|
|
85
87
|
# detects runtime coverage artifacts (Go coverprofile, lcov, coverage.py XML, JaCoCo XML);
|
|
86
88
|
# --generate runs local free coverage tooling where safely supported (Go, JS/TS scripts, pytest-cov XML, JaCoCo)
|
|
87
|
-
opro doctor [--json]
|
|
89
|
+
opro doctor [--proof] [--json] # --proof: why top targets are not Dynamically Proven
|
|
88
90
|
opro update [--force] [--json]
|
|
89
91
|
opro changed --base <ref> [--json]
|
|
90
92
|
opro score [--json]
|
|
@@ -92,9 +94,9 @@ Usage:
|
|
|
92
94
|
# also returns top_risk_gaps: unproven code symbols ranked by OrangePro Risk Score (P × I × D)
|
|
93
95
|
opro record --target-symbol sym:file#Symbol [--test path] [--agent-pass true|false] [--evidence-ids id1,id2] [--provider openai] [--model gpt-4.1] [--prompt-version v1] [--json]
|
|
94
96
|
# record writes static reprove diagnostics only; public Proven requires \`opro prove\`
|
|
95
|
-
opro prove --target-symbol sym:file#Symbol --test path --replacement 'return ...;' [--target-file path] [--method name] [--replacement-mode return-json|promise-json] [--runner auto|vitest|jest|mocha] [--link-node-modules] [--json]
|
|
97
|
+
opro prove --target-symbol sym:file#Symbol --test path --replacement 'return ...;' [--target-file path] [--method name] [--replacement-mode return-json|promise-json] [--runner auto|vitest|jest|mocha|pytest] [--link-node-modules] [--json]
|
|
96
98
|
# runs the dynamic targeted-proof oracle and writes a metadata-only ledger certificate only when baseline-green → mutant assertion-fail closes
|
|
97
|
-
opro prove-loop --target-symbol sym:file#Symbol --test path --replacement 'return ...;' [--setup 'npm run build'] [--setup 'npm ci'] [--setup-timeout-ms 120000] [--source path] [--runner auto|vitest|jest|mocha] [--link-node-modules] [--json]
|
|
99
|
+
opro prove-loop --target-symbol sym:file#Symbol --test path --replacement 'return ...;' [--setup 'npm run build'] [--setup 'npm ci'] [--setup-timeout-ms 120000] [--source path] [--runner auto|vitest|jest|mocha|pytest] [--link-node-modules] [--json]
|
|
98
100
|
# trusted-local wrapper: runs each --setup command in the source checkout, then \`opro prove\` (unchanged oracle + cert), then refreshes the behavior report; setup failure returns unrunnable (never Proven)
|
|
99
101
|
opro recipe db-sqljs --target-symbol sym:file#Class.method --entity file#Entity --out orangepro_generated/<name>.sqljs.spec.ts [--source path] [--seed-field name] [--json]
|
|
100
102
|
# writes a REAL NestJS+TypeORM sqljs integration spec (in-memory) + setup profile; makes a DB-backed baseline runnable so \`opro prove-loop\` can close Proven. Never mocks the target.
|
|
@@ -166,7 +168,7 @@ async function main() {
|
|
|
166
168
|
}
|
|
167
169
|
// Preload configured tree-sitter grammars before analysis so the sync analyzer
|
|
168
170
|
// can extract via AST. Idempotent; only the analysis commands pay for it.
|
|
169
|
-
if (command === "start" || command === "analyze" || command === "update" || command === "generate" || command === "record" || command === "prove" || command === "prove-loop") {
|
|
171
|
+
if (command === "start" || command === "roast" || command === "analyze" || command === "update" || command === "generate" || command === "record" || command === "prove" || command === "prove-loop") {
|
|
170
172
|
await preloadTreeSitter(treeSitterLanguages());
|
|
171
173
|
}
|
|
172
174
|
switch (command) {
|
|
@@ -188,6 +190,7 @@ async function main() {
|
|
|
188
190
|
aiFlows: !asBool(flags["no-ai-flows"], false),
|
|
189
191
|
autoLimit: numericFlag(flags["auto-limit"]),
|
|
190
192
|
noAuto: asBool(flags["no-auto"], false),
|
|
193
|
+
promptVersion: flags["prompt-version"] === "v5" ? "v5" : undefined,
|
|
191
194
|
provider: typeof flags.provider === "string" ? flags.provider : undefined,
|
|
192
195
|
model: typeof flags.model === "string" ? flags.model : undefined
|
|
193
196
|
});
|
|
@@ -274,6 +277,66 @@ async function main() {
|
|
|
274
277
|
}
|
|
275
278
|
return 0;
|
|
276
279
|
}
|
|
280
|
+
case "roast": {
|
|
281
|
+
const source = positionals[0] || ".";
|
|
282
|
+
const limit = numericFlag(flags.limit) ?? numericFlag(flags["auto-limit"]) ?? 5;
|
|
283
|
+
const clearProgress = !json ? installCliProgress("roast") : () => undefined;
|
|
284
|
+
let res;
|
|
285
|
+
try {
|
|
286
|
+
opAnalyze(cwd, { source });
|
|
287
|
+
res = await autoProve(cwd, { autoLimit: limit, existingOnly: true }, { clock: () => new Date().toISOString(), env: process.env, proveLoop: opProveLoop });
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
clearProgress();
|
|
291
|
+
}
|
|
292
|
+
const survived = res.attempts.filter(isRoastSurvivor);
|
|
293
|
+
const payload = {
|
|
294
|
+
status: "roast",
|
|
295
|
+
source,
|
|
296
|
+
attempted: res.attempted,
|
|
297
|
+
dynamically_proven: res.proven,
|
|
298
|
+
survived_mutants: survived.length,
|
|
299
|
+
needs_setup: res.needs_setup,
|
|
300
|
+
survivors: survived.map((a) => ({
|
|
301
|
+
target_symbol: a.target_symbol,
|
|
302
|
+
test_path: a.test_path,
|
|
303
|
+
mutant_status: a.mutant_status,
|
|
304
|
+
reason: a.reason
|
|
305
|
+
}))
|
|
306
|
+
};
|
|
307
|
+
if (json) {
|
|
308
|
+
printJson(payload);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
out("OrangePro roast complete.");
|
|
312
|
+
out(` attempted: ${payload.attempted}`);
|
|
313
|
+
out(` Newly proven this run: ${payload.dynamically_proven}`);
|
|
314
|
+
out(` Survived mutants: ${payload.survived_mutants}`);
|
|
315
|
+
if (survived.length > 0) {
|
|
316
|
+
out("");
|
|
317
|
+
out(`${survived.length} passing test(s) still passed when OrangePro replaced the target with a sentinel mutant:`);
|
|
318
|
+
for (const attempt of survived.slice(0, limit)) {
|
|
319
|
+
out(` - ${attempt.test_path} survived mutant on ${attempt.target_symbol}`);
|
|
320
|
+
if (attempt.reason)
|
|
321
|
+
out(` reason: ${attempt.reason}`);
|
|
322
|
+
}
|
|
323
|
+
out("");
|
|
324
|
+
out("Equivalent mutants can also survive, so treat this as a proof-strengthening queue, not automatic blame.");
|
|
325
|
+
out("Survived mutants are not Dynamically Proven. They are targets for stronger assertions.");
|
|
326
|
+
}
|
|
327
|
+
else if (res.proven > 0) {
|
|
328
|
+
out(" verdict: no survived targeted mutants found in this pass.");
|
|
329
|
+
}
|
|
330
|
+
else if (res.needs_setup.length > 0) {
|
|
331
|
+
const dom = dominantBlockReason(res.needs_setup);
|
|
332
|
+
out(` verdict: proof could not run for ${res.needs_setup.length} target(s)${dom ? `; dominant blocker: ${dom.label}` : ""}.`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
out(" verdict: no survived targeted mutants found in this pass.");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
277
340
|
case "init": {
|
|
278
341
|
const res = opInit(cwd);
|
|
279
342
|
if (json)
|
|
@@ -498,6 +561,24 @@ async function main() {
|
|
|
498
561
|
return 0;
|
|
499
562
|
}
|
|
500
563
|
case "doctor": {
|
|
564
|
+
if (asBool(flags.proof, false)) {
|
|
565
|
+
const res = opProofDoctor(cwd);
|
|
566
|
+
if (json)
|
|
567
|
+
printJson(res);
|
|
568
|
+
else {
|
|
569
|
+
out(res.headline);
|
|
570
|
+
for (const b of res.blockers) {
|
|
571
|
+
out(` ${b.count} target${b.count === 1 ? "" : "s"} blocked by ${b.label}${b.source === "preflight" ? " (preflight — no test was run)" : ""}`);
|
|
572
|
+
out(` e.g. ${b.representative.target_symbol}${b.representative.reason ? ` — ${b.representative.reason}` : ""}`);
|
|
573
|
+
out(` next: ${b.next_step}`);
|
|
574
|
+
}
|
|
575
|
+
for (const nk of res.non_killing) {
|
|
576
|
+
out(` mutant survived: ${nk.target_symbol}${nk.test_path ? ` (test: ${nk.test_path})` : ""}`);
|
|
577
|
+
out(` ${nk.note}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return 0;
|
|
581
|
+
}
|
|
501
582
|
const res = opDoctor(cwd);
|
|
502
583
|
if (json)
|
|
503
584
|
printJson(res);
|
|
@@ -642,15 +723,16 @@ async function main() {
|
|
|
642
723
|
if (flags["replacement-mode"] !== undefined && !replacementMode) {
|
|
643
724
|
throw new Error("--replacement-mode must be one of: return-json, promise-json");
|
|
644
725
|
}
|
|
645
|
-
const proofRunner = flags.runner === undefined ? undefined : flags.runner === "auto" || flags.runner === "vitest" || flags.runner === "jest" || flags.runner === "mocha" ? flags.runner : undefined;
|
|
726
|
+
const proofRunner = flags.runner === undefined ? undefined : flags.runner === "auto" || flags.runner === "vitest" || flags.runner === "jest" || flags.runner === "mocha" || flags.runner === "pytest" ? flags.runner : undefined;
|
|
646
727
|
if (flags.runner !== undefined && !proofRunner) {
|
|
647
|
-
throw new Error("--runner must be one of: auto, vitest, jest, mocha");
|
|
728
|
+
throw new Error("--runner must be one of: auto, vitest, jest, mocha, pytest");
|
|
648
729
|
}
|
|
649
730
|
const res = opDynamicProof(cwd, {
|
|
650
731
|
target_symbol: typeof flags["target-symbol"] === "string" ? flags["target-symbol"] : undefined,
|
|
651
732
|
target_id: typeof flags.target === "string" ? flags.target : undefined,
|
|
652
733
|
source: typeof flags.source === "string" ? flags.source : undefined,
|
|
653
734
|
test_path: typeof flags.test === "string" ? flags.test : "",
|
|
735
|
+
test_run: typeof flags["test-run"] === "string" ? flags["test-run"] : undefined,
|
|
654
736
|
target_path: typeof flags["target-file"] === "string" ? flags["target-file"] : undefined,
|
|
655
737
|
method: typeof flags.method === "string" ? flags.method : undefined,
|
|
656
738
|
replacement: typeof flags.replacement === "string" ? flags.replacement : "",
|
|
@@ -686,15 +768,16 @@ async function main() {
|
|
|
686
768
|
if (flags["replacement-mode"] !== undefined && !replacementMode) {
|
|
687
769
|
throw new Error("--replacement-mode must be one of: return-json, promise-json");
|
|
688
770
|
}
|
|
689
|
-
const proofRunner = flags.runner === undefined ? undefined : flags.runner === "auto" || flags.runner === "vitest" || flags.runner === "jest" || flags.runner === "mocha" ? flags.runner : undefined;
|
|
771
|
+
const proofRunner = flags.runner === undefined ? undefined : flags.runner === "auto" || flags.runner === "vitest" || flags.runner === "jest" || flags.runner === "mocha" || flags.runner === "pytest" ? flags.runner : undefined;
|
|
690
772
|
if (flags.runner !== undefined && !proofRunner) {
|
|
691
|
-
throw new Error("--runner must be one of: auto, vitest, jest, mocha");
|
|
773
|
+
throw new Error("--runner must be one of: auto, vitest, jest, mocha, pytest");
|
|
692
774
|
}
|
|
693
775
|
const res = opProveLoop(cwd, {
|
|
694
776
|
target_symbol: typeof flags["target-symbol"] === "string" ? flags["target-symbol"] : undefined,
|
|
695
777
|
target_id: typeof flags.target === "string" ? flags.target : undefined,
|
|
696
778
|
source: typeof flags.source === "string" ? flags.source : undefined,
|
|
697
779
|
test_path: typeof flags.test === "string" ? flags.test : "",
|
|
780
|
+
test_run: typeof flags["test-run"] === "string" ? flags["test-run"] : undefined,
|
|
698
781
|
target_path: typeof flags["target-file"] === "string" ? flags["target-file"] : undefined,
|
|
699
782
|
method: typeof flags.method === "string" ? flags.method : undefined,
|
|
700
783
|
replacement: typeof flags.replacement === "string" ? flags.replacement : "",
|
package/dist/local/cliArgs.js
CHANGED
|
@@ -209,13 +209,18 @@ function runnerForDynamicProof(framework) {
|
|
|
209
209
|
return "jest";
|
|
210
210
|
if (f.includes("mocha"))
|
|
211
211
|
return "mocha";
|
|
212
|
+
if (f.includes("pytest") || f.includes("python"))
|
|
213
|
+
return "pytest";
|
|
212
214
|
return undefined;
|
|
213
215
|
}
|
|
214
216
|
function symbolFile(target) {
|
|
215
217
|
return target.slice("sym:".length).split("#")[0] ?? "";
|
|
216
218
|
}
|
|
217
219
|
function supportsDynamicProof(target) {
|
|
218
|
-
return /\.[cm]?[jt]sx
|
|
220
|
+
return /\.(?:[cm]?[jt]sx?|py)$/i.test(symbolFile(target));
|
|
221
|
+
}
|
|
222
|
+
function replacementForDynamicProof(target) {
|
|
223
|
+
return /\.py$/i.test(symbolFile(target)) ? "return 0" : "return null;";
|
|
219
224
|
}
|
|
220
225
|
export function runHintsFor(tests, repoRoot, startIndex = 0) {
|
|
221
226
|
return tests.map((t, i) => {
|
|
@@ -236,13 +241,13 @@ export function runHintsFor(tests, repoRoot, startIndex = 0) {
|
|
|
236
241
|
args: {
|
|
237
242
|
target_symbol: target,
|
|
238
243
|
test_path: suggested_path,
|
|
239
|
-
replacement:
|
|
244
|
+
replacement: replacementForDynamicProof(target),
|
|
240
245
|
...(runnerForDynamicProof(t.framework_hint) ? { runner: runnerForDynamicProof(t.framework_hint) } : {})
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
: {
|
|
245
|
-
handoff_note: "Dynamic public Proven currently supports TS/JS CodeSymbol targets only; use record_run for static diagnostics."
|
|
250
|
+
handoff_note: "Dynamic public Proven currently supports TS/JS/Python CodeSymbol targets only; use record_run for static diagnostics."
|
|
246
251
|
}),
|
|
247
252
|
record_run: {
|
|
248
253
|
tool: "orangepro_record_run",
|
|
@@ -285,7 +290,7 @@ export const AGENT_RUN_WORKFLOW = [
|
|
|
285
290
|
"OrangePro returns the test code; YOUR agent runs it. Using your shell tools:",
|
|
286
291
|
"1. Write each test's `body` to its `suggested_path` in the repo.",
|
|
287
292
|
"2. Run its `run_command` from the package that owns the test's directory (monorepos: cd into that package first so the framework's module roots resolve) — or use the repo's own test command (npm test, pytest, npx playwright test).",
|
|
288
|
-
"3. For public Proven, call `
|
|
293
|
+
"3. For public Proven, call `orangepro_prove` with the returned `prove_run` args after the test passes; it reruns baseline + sentinel-mutant and closes only on a dynamic assertion kill. `record_run` is static diagnostics only.",
|
|
289
294
|
"4. Report pass/fail, error messages, and stack traces to the developer; propose a fix for failures.",
|
|
290
295
|
"Runnable Python and Go drafts require local validation tools on PATH (`python3` for pytest syntax checks, `gofmt` for Go syntax checks). If those tools are missing, OrangePro safely returns drafts without run commands.",
|
|
291
296
|
"Requires the repo's test framework + dependencies to already be installed. If a test fails to run for environment reasons (missing framework/deps), that's a local setup issue, not the generated test.",
|
|
@@ -79,6 +79,7 @@ export function makeEdge(input) {
|
|
|
79
79
|
}
|
|
80
80
|
export function makeProofEdges(input) {
|
|
81
81
|
const testExternalId = `test:${input.testRel}`;
|
|
82
|
+
const props = input.properties ? { properties: input.properties } : {};
|
|
82
83
|
return [
|
|
83
84
|
makeEdge({
|
|
84
85
|
from_external_id: input.symId,
|
|
@@ -87,7 +88,8 @@ export function makeProofEdges(input) {
|
|
|
87
88
|
evidence_strength: "hard",
|
|
88
89
|
review_status: "auto_detected",
|
|
89
90
|
provenance: input.provenance,
|
|
90
|
-
last_verified: input.lastVerified
|
|
91
|
+
last_verified: input.lastVerified,
|
|
92
|
+
...props
|
|
91
93
|
}),
|
|
92
94
|
makeEdge({
|
|
93
95
|
from_external_id: testExternalId,
|
|
@@ -96,7 +98,8 @@ export function makeProofEdges(input) {
|
|
|
96
98
|
evidence_strength: "hard",
|
|
97
99
|
review_status: "auto_detected",
|
|
98
100
|
provenance: input.provenance,
|
|
99
|
-
last_verified: input.lastVerified
|
|
101
|
+
last_verified: input.lastVerified,
|
|
102
|
+
...props
|
|
100
103
|
})
|
|
101
104
|
];
|
|
102
105
|
}
|
package/dist/local/ledger.js
CHANGED
|
@@ -48,7 +48,7 @@ export function targetLanguage(symExtId) {
|
|
|
48
48
|
return ext || "unknown";
|
|
49
49
|
}
|
|
50
50
|
export function canReproveLanguage(language) {
|
|
51
|
-
return language === "typescript" || language === "go";
|
|
51
|
+
return language === "typescript" || language === "go" || language === "python";
|
|
52
52
|
}
|
|
53
53
|
export function loadLedger(root) {
|
|
54
54
|
const path = ledgerPath(root);
|
package/dist/local/mcp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import { opAnalyze, opAiFlows, opAiLinks, opChanged, opCompare, opDoctor, opDynamicProof, opExplain, opExport, opGaps, opGenerate, opProveLoop, opRecordRun, opRtm, opScore, opStats, opStart, opStatus, opUpdate, resolveDiffTargets } from "./operations.js";
|
|
4
|
+
import { opAnalyze, opAiFlows, opAiLinks, opChanged, opCompare, opDoctor, opProofDoctor, opDynamicProof, opExplain, opExport, opGaps, opGenerate, opProveLoop, opRecordRun, opRtm, opScore, opStats, opStart, opStatus, opUpdate, resolveDiffTargets } from "./operations.js";
|
|
5
5
|
import { runnableRunHintsFor, AGENT_RUN_WORKFLOW, GROUNDING_CONTRACT } from "./generate/runHints.js";
|
|
6
6
|
import { preloadTreeSitter } from "./analyze/treeSitter/engine.js";
|
|
7
7
|
import { treeSitterLanguages } from "./analyze/treeSitter/languages.js";
|
|
@@ -22,11 +22,11 @@ const DEFAULT_MARKDOWN_RTM_LIMIT = 500;
|
|
|
22
22
|
* Codex, Claude, Copilot, …). First slice exposes NO upload or repo-write tools.
|
|
23
23
|
*/
|
|
24
24
|
export function createLocalServer() {
|
|
25
|
-
const server = new McpServer({ name: "orangepro-local", version: "0.
|
|
25
|
+
const server = new McpServer({ name: "orangepro-local", version: "0.2.0" });
|
|
26
26
|
const root = (ws) => ws || process.cwd();
|
|
27
27
|
server.registerTool("orangepro_start", {
|
|
28
28
|
title: "Start OrangePro",
|
|
29
|
-
description: "One-command local setup for a repo or PR: analyze sources, auto-apply weak AI candidate links and candidate flows when a real BYOK provider is configured, write
|
|
29
|
+
description: "One-command local setup for a repo or PR: analyze sources, auto-apply weak AI candidate links and candidate flows when a real BYOK provider is configured, write behavior-coverage.html + rtm.md, summarize changed/gap targets, and return agent next actions. AI lanes stay separate and never affect Proven coverage.",
|
|
30
30
|
inputSchema: {
|
|
31
31
|
...Workspace,
|
|
32
32
|
source: z.string().optional().describe("Source path to analyze. Defaults to workspace."),
|
|
@@ -38,7 +38,8 @@ export function createLocalServer() {
|
|
|
38
38
|
no_ai_flows: z.boolean().optional().describe("Disable automatic AI candidate-flow discovery while keeping weak AI links enabled."),
|
|
39
39
|
ai_all: z.boolean().optional().describe("Run AI weak-linking over all deterministic behavior nodes instead of gaps-only."),
|
|
40
40
|
provider: z.enum(["openai", "ollama", "anthropic"]).optional().describe("BYOK provider override for the AI passes."),
|
|
41
|
-
model: z.string().optional().describe("Model name override.")
|
|
41
|
+
model: z.string().optional().describe("Model name override."),
|
|
42
|
+
prompt_version: z.enum(["v2", "v5"]).optional().describe("Opt-in generation strategy for the auto-prove generation lane. Default v2/deterministic; v5 uses batched two-phase generation. The prove/mint gate is unchanged either way.")
|
|
42
43
|
},
|
|
43
44
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
44
45
|
}, async (input) => {
|
|
@@ -54,7 +55,8 @@ export function createLocalServer() {
|
|
|
54
55
|
aiAll: input.ai_all,
|
|
55
56
|
aiFlows: !input.no_ai_flows,
|
|
56
57
|
provider: input.provider,
|
|
57
|
-
model: input.model
|
|
58
|
+
model: input.model,
|
|
59
|
+
promptVersion: input.prompt_version
|
|
58
60
|
}));
|
|
59
61
|
}
|
|
60
62
|
catch (error) {
|
|
@@ -124,11 +126,18 @@ export function createLocalServer() {
|
|
|
124
126
|
server.registerTool("orangepro_doctor", {
|
|
125
127
|
title: "Recommend next evidence",
|
|
126
128
|
description: "Recommend the smallest next source that would most improve generated-test quality, with expected score impact. Teaches what data improves output.",
|
|
127
|
-
inputSchema: {
|
|
129
|
+
inputSchema: {
|
|
130
|
+
...Workspace,
|
|
131
|
+
goal: z.string().optional().describe("Optimization goal hint, e.g. 'better_tests'."),
|
|
132
|
+
proof: z
|
|
133
|
+
.boolean()
|
|
134
|
+
.optional()
|
|
135
|
+
.describe("Proof-focused mode: explain why top targets are not Dynamically Proven (deduped blockers + smallest next steps). Read-only; mints nothing.")
|
|
136
|
+
},
|
|
128
137
|
annotations: { readOnlyHint: true, destructiveHint: false }
|
|
129
138
|
}, async (input) => {
|
|
130
139
|
try {
|
|
131
|
-
return asText(opDoctor(root(input.workspace)));
|
|
140
|
+
return asText(input.proof ? opProofDoctor(root(input.workspace)) : opDoctor(root(input.workspace)));
|
|
132
141
|
}
|
|
133
142
|
catch (error) {
|
|
134
143
|
return asError(error);
|
|
@@ -198,12 +207,13 @@ export function createLocalServer() {
|
|
|
198
207
|
target_symbol: z.string().optional().describe("Exact CodeSymbol external id, e.g. sym:src/service.ts#OrderService.createOrder. Preferred."),
|
|
199
208
|
target_id: z.string().optional().describe("Optional graph target id that must resolve to exactly one hard-linked CodeSymbol."),
|
|
200
209
|
source: z.string().optional().describe("Source checkout containing the test and target files. Defaults to workspace."),
|
|
201
|
-
test_path: z.string().describe("
|
|
202
|
-
|
|
210
|
+
test_path: z.string().optional().describe("TS/JS: repo-relative test file to run (required for TS/JS targets)."),
|
|
211
|
+
test_run: z.string().optional().describe("Go: fully-anchored test name for `go test -run`, e.g. '^TestCompute$' (required for Go targets). Java: 'TestClass#testMethod', e.g. 'CalculatorTest#addsTwoNumbers' (required for Java targets)."),
|
|
212
|
+
replacement: z.string().optional().describe("TS/JS: inert sentinel body, e.g. 'return null;' or 'return {\"ok\":false};'. Must be JSON-only per the oracle (required for TS/JS targets). Go derives its own sentinel."),
|
|
203
213
|
target_file: z.string().optional().describe("Optional consistency check. If provided, must match the file derived from target_symbol."),
|
|
204
214
|
method: z.string().optional().describe("Optional consistency check. If provided, must match the member derived from target_symbol."),
|
|
205
215
|
replacement_mode: z.enum(["return-json", "promise-json"]).optional().describe("Sentinel wrapping mode. Default return-json."),
|
|
206
|
-
runner: z.enum(["auto", "vitest", "jest", "mocha"]).optional().describe("Test runner override. Default auto."),
|
|
216
|
+
runner: z.enum(["auto", "vitest", "jest", "mocha", "pytest"]).optional().describe("Test runner override. Default auto."),
|
|
207
217
|
timeout_ms: z.number().int().min(1000).max(600000).optional().describe("Per baseline/mutant test timeout."),
|
|
208
218
|
link_node_modules: z.boolean().optional().describe("Trusted-repo speed mode: symlink node_modules into temp copies."),
|
|
209
219
|
vitest_config: z.string().optional().describe("Repo-relative Vitest config path."),
|
|
@@ -220,6 +230,7 @@ export function createLocalServer() {
|
|
|
220
230
|
target_id: input.target_id,
|
|
221
231
|
source: input.source,
|
|
222
232
|
test_path: input.test_path,
|
|
233
|
+
test_run: input.test_run,
|
|
223
234
|
target_path: input.target_file,
|
|
224
235
|
method: input.method,
|
|
225
236
|
replacement: input.replacement,
|
|
@@ -245,12 +256,13 @@ export function createLocalServer() {
|
|
|
245
256
|
target_symbol: z.string().optional().describe("Exact CodeSymbol external id, e.g. sym:src/service.ts#OrderService.createOrder. Preferred."),
|
|
246
257
|
target_id: z.string().optional().describe("Optional graph target id that must resolve to exactly one hard-linked CodeSymbol."),
|
|
247
258
|
source: z.string().optional().describe("Source checkout containing the test and target files. Setup runs here. Defaults to workspace."),
|
|
248
|
-
test_path: z.string().describe("
|
|
249
|
-
|
|
259
|
+
test_path: z.string().optional().describe("TS/JS: repo-relative test file to run (required for TS/JS targets)."),
|
|
260
|
+
test_run: z.string().optional().describe("Go: fully-anchored test name for `go test -run`, e.g. '^TestCompute$' (required for Go targets). Java: 'TestClass#testMethod', e.g. 'CalculatorTest#addsTwoNumbers' (required for Java targets)."),
|
|
261
|
+
replacement: z.string().optional().describe("TS/JS: inert sentinel body, e.g. 'return null;' or 'return {\"ok\":false};'. Must be JSON-only per the oracle (required for TS/JS targets). Go derives its own sentinel."),
|
|
250
262
|
target_file: z.string().optional().describe("Optional consistency check. If provided, must match the file derived from target_symbol."),
|
|
251
263
|
method: z.string().optional().describe("Optional consistency check. If provided, must match the member derived from target_symbol."),
|
|
252
264
|
replacement_mode: z.enum(["return-json", "promise-json"]).optional().describe("Sentinel wrapping mode. Default return-json."),
|
|
253
|
-
runner: z.enum(["auto", "vitest", "jest", "mocha"]).optional().describe("Test runner override. Default auto."),
|
|
265
|
+
runner: z.enum(["auto", "vitest", "jest", "mocha", "pytest"]).optional().describe("Test runner override. Default auto."),
|
|
254
266
|
timeout_ms: z.number().int().min(1000).max(600000).optional().describe("Per baseline/mutant test timeout."),
|
|
255
267
|
link_node_modules: z.boolean().optional().describe("Trusted-repo speed mode: symlink node_modules into temp copies."),
|
|
256
268
|
vitest_config: z.string().optional().describe("Repo-relative Vitest config path."),
|
|
@@ -275,6 +287,7 @@ export function createLocalServer() {
|
|
|
275
287
|
target_id: input.target_id,
|
|
276
288
|
source: input.source,
|
|
277
289
|
test_path: input.test_path,
|
|
290
|
+
test_run: input.test_run,
|
|
278
291
|
target_path: input.target_file,
|
|
279
292
|
method: input.method,
|
|
280
293
|
replacement: input.replacement,
|