@orangepro/orangepro-mcp 0.2.33 → 0.2.34
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/local/generate/generator.js +21 -1
- package/dist/local/operations.js +61 -25
- package/package.json +1 -1
|
@@ -2169,7 +2169,10 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2169
2169
|
const systemPrompt = opts.systemPrompt ?? buildSystemPrompt();
|
|
2170
2170
|
const promptVersion = inputMode === "graph_grounded" && opts.prompt_version === "v5" ? PROMPT_VERSION_V5 : PROMPT_VERSION;
|
|
2171
2171
|
const created_at = clock();
|
|
2172
|
-
const runSeed = shortHash(created_at +
|
|
2172
|
+
const runSeed = shortHash(created_at +
|
|
2173
|
+
provider.modelName +
|
|
2174
|
+
runTargets.map((t) => t.external_id).join(",") +
|
|
2175
|
+
JSON.stringify(opts.existing_generated_test_titles ?? []));
|
|
2173
2176
|
const run_id = `local-gen-${runSeed}`;
|
|
2174
2177
|
const generated = [];
|
|
2175
2178
|
const missing = [];
|
|
@@ -2287,6 +2290,10 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2287
2290
|
if (generated.length >= limit)
|
|
2288
2291
|
break;
|
|
2289
2292
|
const gc = gatherContext(graph, behavior, framework, fileReader);
|
|
2293
|
+
const existingGeneratedTitles = dedupe(opts.existing_generated_test_titles ?? []);
|
|
2294
|
+
if (existingGeneratedTitles.length) {
|
|
2295
|
+
gc.ctx.existing_tests = dedupe([...gc.ctx.existing_tests, ...existingGeneratedTitles]);
|
|
2296
|
+
}
|
|
2290
2297
|
reportProgress(`Planning "${gc.ctx.behavior_title}" [v5]…`);
|
|
2291
2298
|
let scenarios = [];
|
|
2292
2299
|
// Transport first: a network/timeout failure is NOT malformed JSON, so it does
|
|
@@ -2377,6 +2384,19 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2377
2384
|
continue;
|
|
2378
2385
|
}
|
|
2379
2386
|
}
|
|
2387
|
+
if (existingGeneratedTitles.length) {
|
|
2388
|
+
const normalizedExistingTitles = new Set(existingGeneratedTitles.map((title) => title.trim().toLowerCase()));
|
|
2389
|
+
const beforeDuplicateFilter = scenarios.length;
|
|
2390
|
+
scenarios = scenarios.filter((scenario) => {
|
|
2391
|
+
const scenarioTitle = scenario.title.trim().toLowerCase();
|
|
2392
|
+
const fullTitle = `${gc.ctx.behavior_title} — ${scenario.title}`.trim().toLowerCase();
|
|
2393
|
+
return !normalizedExistingTitles.has(scenarioTitle) && !normalizedExistingTitles.has(fullTitle);
|
|
2394
|
+
});
|
|
2395
|
+
const duplicateCount = beforeDuplicateFilter - scenarios.length;
|
|
2396
|
+
if (duplicateCount > 0) {
|
|
2397
|
+
warnings.push(`Dropped ${duplicateCount} already-generated v5 scenario(s) for "${gc.ctx.behavior_title}" during top-up.`);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2380
2400
|
if (scenarios.length === 0) {
|
|
2381
2401
|
missing.push({
|
|
2382
2402
|
external_id: behavior.external_id,
|
package/dist/local/operations.js
CHANGED
|
@@ -1669,37 +1669,67 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
|
|
|
1669
1669
|
else if (!opts.noAuto && generationProviderConfigured && opts.ai !== false && generationLimit > 0) {
|
|
1670
1670
|
try {
|
|
1671
1671
|
const graphForGeneration = loadGraph(workspacePaths(root).graphPath);
|
|
1672
|
-
const
|
|
1673
|
-
const
|
|
1672
|
+
const generatedTestsByTarget = new Map();
|
|
1673
|
+
for (const test of graphForGeneration.generated_tests ?? []) {
|
|
1674
|
+
const targetId = test.target_symbol_external_id;
|
|
1675
|
+
if (!targetId || test.stale === true)
|
|
1676
|
+
continue;
|
|
1677
|
+
const tests = generatedTestsByTarget.get(targetId) ?? [];
|
|
1678
|
+
tests.push(test);
|
|
1679
|
+
generatedTestsByTarget.set(targetId, tests);
|
|
1680
|
+
}
|
|
1681
|
+
const targetPlans = rankPriorityGaps(graphForGeneration, {
|
|
1674
1682
|
repoRoot: root,
|
|
1675
1683
|
limit: generationLimit,
|
|
1676
1684
|
provenIds: provenSymbolIds(graphForGeneration, loadLedger(root))
|
|
1677
1685
|
})
|
|
1678
|
-
.map((gap) =>
|
|
1679
|
-
.
|
|
1680
|
-
|
|
1686
|
+
.map((gap) => {
|
|
1687
|
+
const existingTests = generatedTestsByTarget.get(gap.id) ?? [];
|
|
1688
|
+
return {
|
|
1689
|
+
id: gap.id,
|
|
1690
|
+
existingTests,
|
|
1691
|
+
deficit: Math.max(0, 2 - existingTests.length)
|
|
1692
|
+
};
|
|
1693
|
+
})
|
|
1694
|
+
.filter((target) => target.deficit > 0);
|
|
1695
|
+
if (!targetPlans.length) {
|
|
1681
1696
|
generationResult = {
|
|
1682
1697
|
...generationResult,
|
|
1683
1698
|
status: "no_targets",
|
|
1684
|
-
reason: "
|
|
1699
|
+
reason: "Every eligible priority flow already has two generated tests."
|
|
1685
1700
|
};
|
|
1686
1701
|
}
|
|
1687
1702
|
else {
|
|
1688
|
-
reportProgress(`generate:
|
|
1703
|
+
reportProgress(`generate: filling test gaps for ${targetPlans.length} priority flow(s)`, { current: 6, total: 8 });
|
|
1689
1704
|
const generatedDrafts = [];
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1705
|
+
const incompleteTargets = [];
|
|
1706
|
+
for (const target of targetPlans) {
|
|
1707
|
+
let remaining = target.deficit;
|
|
1708
|
+
const existingTitles = target.existingTests.map((test) => test.title);
|
|
1709
|
+
// A provider can return one accepted scenario when two were requested.
|
|
1710
|
+
// Make at most one bounded follow-up for the remaining slot, carrying
|
|
1711
|
+
// prior titles so the planner cannot silently duplicate the first test.
|
|
1712
|
+
for (let attempt = 0; attempt < 2 && remaining > 0; attempt++) {
|
|
1713
|
+
const generated = await opGenerate(root, {
|
|
1714
|
+
...providerOpts,
|
|
1715
|
+
target_ids: [target.id],
|
|
1716
|
+
limit: remaining,
|
|
1717
|
+
pin_unchanged: false,
|
|
1718
|
+
existing_generated_test_titles: existingTitles,
|
|
1719
|
+
// The offline deterministic stand-in emits the established v2 scaffold; v5 is
|
|
1720
|
+
// a two-phase model planning protocol and must not be selected implicitly for it.
|
|
1721
|
+
prompt_version: opts.promptVersion ?? (deterministicGeneration ? "v2" : "v5")
|
|
1722
|
+
}, providerDeps);
|
|
1723
|
+
const freshForTarget = generated.generated_tests.filter((test) => test.target_symbol_external_id === target.id && !test.pinned);
|
|
1724
|
+
generatedDrafts.push(...freshForTarget);
|
|
1725
|
+
warnings.push(...generated.warnings.map((w) => `generate: ${w}`));
|
|
1726
|
+
if (freshForTarget.length === 0)
|
|
1727
|
+
break;
|
|
1728
|
+
existingTitles.push(...freshForTarget.map((test) => test.title));
|
|
1729
|
+
remaining = Math.max(0, remaining - freshForTarget.length);
|
|
1730
|
+
}
|
|
1731
|
+
if (remaining > 0)
|
|
1732
|
+
incompleteTargets.push(target.id);
|
|
1703
1733
|
}
|
|
1704
1734
|
const blockers = {};
|
|
1705
1735
|
for (const draft of generatedDrafts.filter((test) => test.runnable === false)) {
|
|
@@ -1707,22 +1737,27 @@ export async function opStart(root, opts = {}, deps = defaultDeps()) {
|
|
|
1707
1737
|
blockers[blocker] = (blockers[blocker] ?? 0) + 1;
|
|
1708
1738
|
}
|
|
1709
1739
|
const runnable = generatedDrafts.filter((test) => test.runnable !== false).length;
|
|
1740
|
+
const shortfallReason = incompleteTargets.length
|
|
1741
|
+
? `${incompleteTargets.length} priority flow(s) remain below two generated tests because the provider returned no additional accepted distinct scenario.`
|
|
1742
|
+
: undefined;
|
|
1710
1743
|
generationResult = {
|
|
1711
1744
|
status: generatedDrafts.length === 0
|
|
1712
1745
|
? "no_results"
|
|
1713
|
-
: generatedDrafts.some((test) => test.runnable === false)
|
|
1746
|
+
: generatedDrafts.some((test) => test.runnable === false) || incompleteTargets.length > 0
|
|
1714
1747
|
? "completed_with_blockers"
|
|
1715
1748
|
: "completed",
|
|
1716
|
-
requested:
|
|
1749
|
+
requested: targetPlans.length,
|
|
1717
1750
|
generated: generatedDrafts.length,
|
|
1718
1751
|
runnable,
|
|
1719
1752
|
drafts: generatedDrafts.length - runnable,
|
|
1720
1753
|
blockers,
|
|
1721
1754
|
...(generatedDrafts.length === 0
|
|
1722
|
-
? { reason: "The provider returned no generated-test drafts for the selected risk targets." }
|
|
1723
|
-
:
|
|
1755
|
+
? { reason: shortfallReason ?? "The provider returned no generated-test drafts for the selected risk targets." }
|
|
1756
|
+
: shortfallReason
|
|
1757
|
+
? { reason: shortfallReason }
|
|
1758
|
+
: {})
|
|
1724
1759
|
};
|
|
1725
|
-
if (
|
|
1760
|
+
if (generationResult.reason)
|
|
1726
1761
|
warnings.push(`generate: ${generationResult.reason}`);
|
|
1727
1762
|
}
|
|
1728
1763
|
}
|
|
@@ -1948,6 +1983,7 @@ export async function opGenerate(root, opts = {}, deps = defaultDeps()) {
|
|
|
1948
1983
|
limit: opts.limit,
|
|
1949
1984
|
input_mode: opts.input_mode,
|
|
1950
1985
|
prompt_version: opts.prompt_version,
|
|
1986
|
+
existing_generated_test_titles: opts.existing_generated_test_titles,
|
|
1951
1987
|
// Persisting lane: a runnable draft for an unchanged target is reused as-is
|
|
1952
1988
|
// rather than re-bought from the model on every run.
|
|
1953
1989
|
pin_unchanged: opts.pin_unchanged ?? true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangepro/orangepro-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.34",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OrangePro (`opro`) — a local-first, BYOK CLI + MCP server that builds an evidence graph from a local checkout, ingests runtime coverage, and generates grounded tests. Metadata-only exports; no source upload; generated tests stay local.",
|
|
6
6
|
"license": "MIT",
|