@orangepro/orangepro-mcp 0.2.29 → 0.2.30
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/README.md +201 -198
- package/dist/local/generate/generator.js +98 -15
- package/dist/local/generate/prompt.js +23 -4
- package/dist/local/generate/promptV5.js +24 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,117 +1,126 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://github.com/OrangeproAI/orangepro-mcp/raw/main/docs/logo-horizontal.svg" alt="OrangePro" width="320" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Find the behaviors your tests miss. Generate grounded tests that actually run.</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@orangepro/mcp-server"><img src="https://badge.fury.io/js/@orangepro%2Fmcp-server.svg" alt="npm version" /></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="MIT License" /></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/@orangepro/mcp-server"><img src="https://img.shields.io/npm/dw/@orangepro/mcp-server.svg" alt="npm downloads" /></a>
|
|
13
|
+
<a href="https://glama.ai/mcp/servers/OrangeproAI/orangepro-mcp"><img src="https://glama.ai/mcp/servers/OrangeproAI/orangepro-mcp/badges/score.svg" alt="Glama score" /></a>
|
|
14
|
+
<a href="https://registry.modelcontextprotocol.io/?q=orangepro"><img src="https://img.shields.io/badge/MCP_Registry-orangepro-orange.svg" alt="MCP Registry" /></a>
|
|
15
|
+
</p>
|
|
2
16
|
|
|
3
|
-
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.npmjs.com/package/@orangepro/mcp-server )
|
|
6
|
-
[](https://glama.ai/mcp/servers/OrangeproAI/orangepro-mcp )
|
|
7
|
-
[](https://registry.modelcontextprotocol.io/?q=orangepro )
|
|
8
|
-
|
|
9
|
-
**Find the behaviors your tests miss. Generate grounded tests that actually run.**
|
|
10
|
-
|
|
11
|
-
`opro` builds a knowledge graph from your local checkout, maps every behavior in your code, shows which ones are tested and which aren't, and generates integration-level tests grounded in real symbols — not hallucinated imports. It runs as a CLI and a local stdio MCP server.
|
|
17
|
+
---
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
**<a href="https://orangeproai.github.io/orangepro-mcp/twenty-crm-behavior-coverage.html" target="_blank">→ Live example: Twenty CRM behavior coverage report</a>**
|
|
19
|
+
OrangePro maps every public behavior in your codebase, scores each one by real test evidence, and shows you the structural blind spots before your users find them. Runs locally. Your code never leaves your machine.
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npx -y @orangepro/mcp-server@latest start .
|
|
23
|
+
```
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
<!-- TODO: Replace with a terminal GIF showing the command running and report opening -->
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
---
|
|
21
28
|
|
|
29
|
+
## Table of Contents
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
- [What you get](#what-you-get)
|
|
32
|
+
- [Evidence tiers](#evidence-tiers)
|
|
33
|
+
- [Quick start](#quick-start)
|
|
34
|
+
- [Use with your coding agent](#use-with-your-coding-agent)
|
|
35
|
+
- [How it works](#how-it-works)
|
|
36
|
+
- [Language support](#language-support)
|
|
37
|
+
- [Privacy](#privacy)
|
|
38
|
+
- [CLI reference](#cli-reference)
|
|
39
|
+
- [MCP tools](#mcp-tools-18-total)
|
|
40
|
+
- [Platform](#whats-on-the-hosted-platform)
|
|
41
|
+
- [Contributing](#contributing)
|
|
24
42
|
|
|
25
43
|
---
|
|
26
|
-
Install the target repository's dependencies first, then run OrangePro from that repository:
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
cd /path/to/your/repo
|
|
30
|
-
npm install # or pnpm install / bun install / the repository's package manager
|
|
45
|
+
## What you get
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
export ANTHROPIC_API_KEY="..." # or OPENAI_API_KEY / OLLAMA_BASE_URL
|
|
47
|
+
One command produces an interactive HTML report:
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
```bash
|
|
50
|
+
npx -y @orangepro/mcp-server@latest start .
|
|
36
51
|
open .orangepro/behavior-coverage.html
|
|
37
52
|
```
|
|
53
|
+
The report has two modes: **Simple** (integration-level blind spots, plain English) and **Expert** (full behavior list, evidence tiers, flows, system map). Toggle with the pill switch at the top.
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
**<a href="https://orangeproai.github.io/orangepro-mcp/twenty-crm-behavior-coverage.html" target="_blank">→ Live example: Twenty CRM (5,237 behaviors mapped)</a>**
|
|
40
56
|
|
|
41
|
-
|
|
57
|
+
<img width="895" alt="OrangePro system map — entry lanes, services, evidence tiers" src="https://github.com/user-attachments/assets/1ceba779-e0ec-4ec1-99ce-001bc3589b42](https://github.com/user-attachments/assets/a4d85b98-4f19-4647-8dd9-db5911574f49" />
|
|
42
58
|
|
|
43
|
-
|
|
44
|
-
.orangepro/
|
|
45
|
-
├── behavior-coverage.html ← open this: system map, risks, flows, behaviors
|
|
46
|
-
├── graph.json ← deterministic evidence graph
|
|
47
|
-
├── COVERAGE_REPORT.md ← coverage and gap summary
|
|
48
|
-
├── rtm.md ← requirements traceability matrix
|
|
49
|
-
└── ai/ ← candidate AI links/flows when a provider is configured
|
|
59
|
+
*System map — entry lanes (GraphQL, HTTP, Jobs) flowing into services, sized by traffic, colored by evidence tier, red-ringed by risk.*
|
|
50
60
|
|
|
51
|
-
orangepro_generated/ ← contained generated tests; existing source files are untouched
|
|
52
|
-
```
|
|
53
61
|
|
|
54
|
-
|
|
62
|
+
<img width="818" alt="Priority gaps" src="https://github.com/user-attachments/assets/30a512b6-7830-48db-a00f-a616e7176ea8" />
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
*Priority gaps of another open source Project HONO — top 20 unproven behaviors ranked by blast radius, with generated test drafts.*
|
|
57
65
|
|
|
58
66
|
---
|
|
59
67
|
|
|
60
|
-
##
|
|
68
|
+
## Evidence tiers
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
# No install needed: run the full local workflow in the current repository
|
|
64
|
-
npx -y @orangepro/mcp-server@latest start . --prompt-version v5
|
|
70
|
+
Every behavior gets exactly one tier. Nothing is labeled "tested" on faith.
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
| Tier | Color | What it means |
|
|
73
|
+
|------|-------|---------------|
|
|
74
|
+
| **Dynamically Proven** | 🟢 | A real test kills a targeted mutation of this behavior |
|
|
75
|
+
| **Runtime-covered** | 🟢 | Coverage tool executed this code |
|
|
76
|
+
| **Statically Linked** | 🟡 | A test imports and calls this code — structural link, not proof |
|
|
77
|
+
| **Unconfirmed Candidate** | ⚪ | A similar test file exists — a lead, not evidence |
|
|
78
|
+
| **No Signal** | 🔴 | Nothing tests this behavior |
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
git clone https://github.com/OrangeproAI/orangepro-mcp.git
|
|
72
|
-
cd orangepro-mcp && npm ci && npm run build && npm link
|
|
73
|
-
```
|
|
80
|
+
> **"Dynamically Proven 0" is normal on first run.** Proof requires running tests against targeted mutations. That's the trust model.
|
|
74
81
|
|
|
75
82
|
---
|
|
76
83
|
|
|
77
|
-
##
|
|
84
|
+
## Quick start
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
cd /path/to/your/repo
|
|
88
|
+
npm install # install the repo's own dependencies first
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
npx -y @orangepro/mcp-server@latest start .
|
|
91
|
+
open .orangepro/behavior-coverage.html
|
|
92
|
+
```
|
|
80
93
|
|
|
81
|
-
|
|
94
|
+
No API key needed. The report shows your system map, evidence tiers, priority gaps, and delta since last run.
|
|
82
95
|
|
|
83
|
-
|
|
96
|
+
**Want test generation?** Add a model key (BYOK):
|
|
84
97
|
|
|
85
98
|
```bash
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
opro agent --client cursor
|
|
89
|
-
opro agent --client opencode
|
|
90
|
-
opro agent --client generic
|
|
99
|
+
export ANTHROPIC_API_KEY="..." # or OPENAI_API_KEY / OLLAMA_BASE_URL
|
|
100
|
+
npx -y @orangepro/mcp-server@latest start .
|
|
91
101
|
```
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
AI output never changes evidence tiers. Only the mutation-kill oracle can mint Dynamically Proven.
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
# Codex
|
|
97
|
-
npx -y @orangepro/mcp-server@latest agent --client codex
|
|
105
|
+
**Output:**
|
|
98
106
|
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
```
|
|
108
|
+
.orangepro/
|
|
109
|
+
├── behavior-coverage.html ← open this
|
|
110
|
+
├── graph.json ← deterministic evidence graph
|
|
111
|
+
├── COVERAGE_REPORT.md ← coverage and gap summary
|
|
112
|
+
└── ai/ ← candidate flows (when a key is configured)
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
orangepro_generated/ ← generated tests; your source files are never touched
|
|
115
|
+
```
|
|
104
116
|
|
|
105
|
-
|
|
106
|
-
npx -y @orangepro/mcp-server@latest agent --client opencode
|
|
117
|
+
Each rerun shows a **delta banner**: what entered the codebase, what moved up in risk, what got resolved.
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
npx -y @orangepro/mcp-server@latest agent --client generic
|
|
110
|
-
```
|
|
119
|
+
---
|
|
111
120
|
|
|
112
|
-
|
|
121
|
+
## Use with your coding agent
|
|
113
122
|
|
|
114
|
-
Add to your client's
|
|
123
|
+
OrangePro runs as an MCP server. Add to your client's config:
|
|
115
124
|
|
|
116
125
|
```json
|
|
117
126
|
{
|
|
@@ -124,48 +133,83 @@ Add to your client's MCP config:
|
|
|
124
133
|
}
|
|
125
134
|
```
|
|
126
135
|
|
|
127
|
-
| Client |
|
|
136
|
+
| Client | Where to put it |
|
|
128
137
|
| --- | --- |
|
|
129
138
|
| Claude Code | `.mcp.json` or `~/.claude.json` |
|
|
130
139
|
| Cursor | `~/.cursor/mcp.json` or Settings → MCP |
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
| OpenCode | Config printed by `opro agent --client opencode` |
|
|
134
|
-
|
|
135
|
-
### The workflow
|
|
140
|
+
| VS Code / Copilot | MCP settings |
|
|
141
|
+
| Codex / OpenCode | Run `npx -y @orangepro/mcp-server@latest agent --client codex` |
|
|
136
142
|
|
|
137
|
-
Tell your agent:
|
|
143
|
+
**The workflow:** Tell your agent:
|
|
138
144
|
|
|
139
145
|
> "Use `orangepro_start`, then `orangepro_generate_tests` with base_ref=main. Write each test to its suggested_path, run it, and report pass/fail."
|
|
140
146
|
|
|
141
147
|
The agent writes the test, runs it, calls `orangepro_prove`, and the behavior turns Dynamically Proven. One prompt, full loop.
|
|
142
148
|
|
|
143
|
-
|
|
149
|
+
---
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
151
|
+
## Works with
|
|
152
|
+
|
|
153
|
+
<p>
|
|
154
|
+
<strong>Claude Code</strong> · <strong>Cursor</strong> · <strong>GitHub Copilot</strong> · <strong>Codex</strong> · <strong>Windsurf</strong> · <strong>OpenCode</strong> · <strong>VS Code</strong>
|
|
155
|
+
</p>
|
|
156
|
+
|
|
157
|
+
Any MCP-compatible agent can drive OrangePro. No vendor lock-in.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## How it works
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
|
165
|
+
│ Your Code │ ──► │ Knowledge │ ──► │ Evidence │
|
|
166
|
+
│ (any lang) │ │ Graph │ │ Tiers │
|
|
167
|
+
└─────────────┘ └──────────────┘ └─────────────┘
|
|
168
|
+
│
|
|
169
|
+
┌──────┴──────┐
|
|
170
|
+
▼ ▼
|
|
171
|
+
┌───────────┐ ┌──────────┐
|
|
172
|
+
│ Gap Report│ │ Generate │
|
|
173
|
+
│ + Risks │ │ Tests │
|
|
174
|
+
└───────────┘ └──────────┘
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
| Phase | What happens | Needs a model key? |
|
|
178
|
+
|-------|-------------|-------------------|
|
|
179
|
+
| **Analyze** | AST walk → behaviors, flows, evidence tiers | No |
|
|
180
|
+
| **Score** | Graph readiness score (0–100) | No |
|
|
181
|
+
| **Generate** | Grounded tests for top gaps | Yes (BYOK) |
|
|
182
|
+
| **Prove** | Mutation-kill oracle confirms test breaks if behavior changes | No |
|
|
183
|
+
|
|
184
|
+
Same code = same score. Deterministic. Always.
|
|
165
185
|
|
|
166
186
|
---
|
|
167
187
|
|
|
168
|
-
##
|
|
188
|
+
## Language support
|
|
189
|
+
|
|
190
|
+
| Language | Static mapping | Generated tests | Dynamic proof |
|
|
191
|
+
|----------|:-:|:-:|:-:|
|
|
192
|
+
| TypeScript / JavaScript | ✓ | ✓ Jest / Vitest / Mocha | ✓ |
|
|
193
|
+
| Python | ✓ | ✓ pytest | ✓ |
|
|
194
|
+
| Go | ✓ | ✓ `*_test.go` | ✓ |
|
|
195
|
+
| Java | ✓ | ✓ JUnit 4/5 | ✓ |
|
|
196
|
+
| Kotlin, Rust, PHP, C#, Ruby, Swift, C, C++ | ✓ | planned | planned |
|
|
197
|
+
|
|
198
|
+
Static mapping works across many languages via tree-sitter. Dynamic proof is deliberately narrower — each language needs a runner, mutation locator, and sandbox profile.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Privacy
|
|
203
|
+
|
|
204
|
+
- **No stored source.** Reads code in-process. Never uploads to an OrangePro server.
|
|
205
|
+
- **No existing-source mutation.** Never edits your source or test files.
|
|
206
|
+
- **Your keys stay yours.** Read from env at call time, never persisted.
|
|
207
|
+
- **BYOK is direct.** Code context goes to the model provider you configure. OrangePro is not in that path.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
<details>
|
|
212
|
+
<summary><strong>CLI reference</strong></summary>
|
|
169
213
|
|
|
170
214
|
```bash
|
|
171
215
|
opro # analyze + report + agent next actions
|
|
@@ -175,39 +219,66 @@ opro score # graph readiness (0–100)
|
|
|
175
219
|
opro gaps --limit 10 # top 10 untested behaviors
|
|
176
220
|
opro generate --base main # tests for PR diff
|
|
177
221
|
opro generate --single # top gap, whole repo
|
|
178
|
-
opro prove # mutation-kill oracle
|
|
222
|
+
opro prove # mutation-kill oracle
|
|
179
223
|
opro rtm # traceability matrix
|
|
180
224
|
opro export # metadata-only evidence pack
|
|
181
225
|
opro mcp # run as MCP server (stdio)
|
|
182
226
|
opro doctor # what evidence to add next
|
|
183
|
-
opro doctor --proof # explain why dynamic proof could not close
|
|
184
227
|
opro coverage # ingest runtime coverage
|
|
185
228
|
```
|
|
186
229
|
|
|
187
230
|
Add `--json` to any read command for machine output. Run `opro help` for the full reference.
|
|
188
231
|
|
|
189
|
-
|
|
232
|
+
</details>
|
|
233
|
+
|
|
234
|
+
<details>
|
|
235
|
+
<summary><strong>MCP tools (18 total)</strong></summary>
|
|
236
|
+
|
|
237
|
+
| Tool | What it does |
|
|
238
|
+
|------|--------------|
|
|
239
|
+
| `orangepro_start` | One-command setup: analyze + report + next actions |
|
|
240
|
+
| `orangepro_analyze_sources` | Build/refresh the evidence graph |
|
|
241
|
+
| `orangepro_generate_tests` | Generate grounded tests for gaps |
|
|
242
|
+
| `orangepro_prove` | Run mutation-kill oracle on a behavior |
|
|
243
|
+
| `orangepro_prove_loop` | Setup + dynamic proof + report refresh for one behavior |
|
|
244
|
+
| `orangepro_find_test_gaps` | List behaviors with weak/missing tests, ranked by risk |
|
|
245
|
+
| `orangepro_graph_score` | Graph readiness score (0–100) |
|
|
246
|
+
| `orangepro_status` | Workspace state without generating anything |
|
|
247
|
+
| `orangepro_doctor` | Recommend next evidence to improve quality |
|
|
248
|
+
| `orangepro_rtm` | Requirements traceability matrix |
|
|
249
|
+
| `orangepro_stats` | Aggregate statistics |
|
|
250
|
+
| `orangepro_changed_impact` | What a diff touches (requires git + base ref) |
|
|
251
|
+
| `orangepro_record_run` | Record a test run result |
|
|
252
|
+
| `orangepro_explain_test` | Explain why a test was generated |
|
|
253
|
+
| `orangepro_export_evidence_pack` | Export metadata-only evidence pack |
|
|
254
|
+
| `orangepro_update_graph` | Incremental graph update |
|
|
255
|
+
| `orangepro_ai_links` | Weak behavior→symbol suggestions (optional AI) |
|
|
256
|
+
| `orangepro_ai_flows` | Candidate flow discovery (optional AI) |
|
|
257
|
+
|
|
258
|
+
</details>
|
|
190
259
|
|
|
191
|
-
|
|
260
|
+
<details>
|
|
261
|
+
<summary><strong>PR workflow</strong></summary>
|
|
192
262
|
|
|
193
263
|
```bash
|
|
194
264
|
opro generate --base main # tests for what this branch changed
|
|
195
|
-
opro generate --pr 1234 # checks out PR #1234
|
|
265
|
+
opro generate --pr 1234 # checks out PR #1234
|
|
196
266
|
opro generate --changed # current branch diff vs main
|
|
197
267
|
```
|
|
198
268
|
|
|
199
269
|
Each generated test includes:
|
|
200
270
|
- **Grounding** — the real files, symbols, and existing tests it cites
|
|
201
271
|
- **Run hints** — where to write it, how to run it
|
|
202
|
-
- **Scenario bucket
|
|
272
|
+
- **Scenario bucket** — what failure mode it targets
|
|
203
273
|
|
|
204
|
-
If
|
|
274
|
+
If dependencies aren't installed, tests are kept as **Manual tests** (Given/When/Then steps with the blocker named). Install dependencies and re-run to convert them to runnable tests.
|
|
205
275
|
|
|
206
|
-
|
|
276
|
+
</details>
|
|
207
277
|
|
|
208
|
-
|
|
278
|
+
<details>
|
|
279
|
+
<summary><strong>Test categories</strong></summary>
|
|
209
280
|
|
|
210
|
-
Generation is evidence-gated. A category is produced only when the graph has supporting evidence
|
|
281
|
+
Generation is evidence-gated. A category is produced only when the graph has supporting evidence.
|
|
211
282
|
|
|
212
283
|
| Category | What it targets |
|
|
213
284
|
|----------|-----------------|
|
|
@@ -218,43 +289,10 @@ Generation is evidence-gated. A category is produced only when the graph has sup
|
|
|
218
289
|
| Security / privacy | Auth, injection, data leakage |
|
|
219
290
|
| Regression | Pinning a previously-broken behavior |
|
|
220
291
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
## Evidence tiers
|
|
224
|
-
|
|
225
|
-
Every behavior gets exactly one tier. Nothing is labeled "tested" on faith.
|
|
226
|
-
|
|
227
|
-
| Tier | What it means | How you get there |
|
|
228
|
-
|------|---------------|------------------|
|
|
229
|
-
| **Dynamically Proven** | A real test kills a targeted mutant of this behavior | `opro prove` after writing/running a test |
|
|
230
|
-
| **Runtime-covered** | Coverage tool executed this code | `opro start --generate-coverage` |
|
|
231
|
-
| **Statically Linked** | A test **imports and calls** this code — a hard structural link | Automatic during analysis |
|
|
232
|
-
| **Unconfirmed Candidate** | A lexically similar test file exists, but nothing links it — a lead, **not evidence** | Automatic; upgrade it by writing the linking test |
|
|
233
|
-
| **No Signal** | Nothing tests this behavior yet | — |
|
|
234
|
-
|
|
235
|
-
> **"Dynamically Proven 0" is normal on first run.** Static analysis always runs. Dynamic proof requires running tests against targeted mutations. That's the trust model — nothing is Dynamically Proven until a real test kills a real mutant.
|
|
292
|
+
</details>
|
|
236
293
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
## Language support
|
|
242
|
-
|
|
243
|
-
OrangePro separates static mapping, generated tests, runtime coverage, and dynamic proof. Those are different confidence bars.
|
|
244
|
-
|
|
245
|
-
| Language | Static behavior extraction | Generated tests | Runtime coverage | Dynamic proof |
|
|
246
|
-
|----------|:--------------------------:|:---------------:|:----------------:|:-------------:|
|
|
247
|
-
| TypeScript / JavaScript | ✓ | ✓ Jest / Vitest / Mocha / AVA-style drafts | ✓ lcov.info | ✓ Vitest / Jest / Mocha |
|
|
248
|
-
| Python | ✓ | ✓ pytest | ✓ coverage.py / pytest-cov XML | ✓ pytest |
|
|
249
|
-
| Go | ✓ | ✓ same-package `*_test.go` | ✓ coverprofile | ✓ `go test` |
|
|
250
|
-
| Java | ✓ | ✓ JUnit 4/5 | ✓ JaCoCo XML | ✓ Maven/JUnit |
|
|
251
|
-
| Kotlin, Rust, PHP, C#, Ruby, Swift, C, C++ | ✓ static behavior extraction | planned | planned where standard coverage exists | planned proof profiles |
|
|
252
|
-
|
|
253
|
-
Static mapping works across many languages through tree-sitter and repo metadata. Dynamic proof is deliberately narrower: each language needs a runner, mutation locator, sandbox profile, and false-proof regressions before it can mint Dynamically Proven.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Model setup (BYOK)
|
|
294
|
+
<details>
|
|
295
|
+
<summary><strong>Model setup (BYOK)</strong></summary>
|
|
258
296
|
|
|
259
297
|
Analysis, scoring, and proof need no model key. Generation does.
|
|
260
298
|
|
|
@@ -268,56 +306,20 @@ Auto-detect order: OpenAI → Ollama → Anthropic. Override with `--provider` a
|
|
|
268
306
|
|
|
269
307
|
Run `opro setup` to configure interactively. Keys stay in your environment — never written to graph, config, or artifacts.
|
|
270
308
|
|
|
271
|
-
|
|
309
|
+
</details>
|
|
272
310
|
|
|
273
|
-
|
|
311
|
+
<details>
|
|
312
|
+
<summary><strong>AI candidate lanes</strong></summary>
|
|
274
313
|
|
|
275
|
-
With a provider key, OrangePro
|
|
314
|
+
With a provider key, OrangePro stages weak AI behavior→symbol links and AI-suggested candidate flows. These are review/generation worklists, not evidence:
|
|
276
315
|
|
|
277
316
|
- AI links appear as `AI-linked` suggestions.
|
|
278
317
|
- AI flows are stored separately from deterministic flows.
|
|
279
|
-
- Neither lane changes
|
|
318
|
+
- Neither lane changes evidence tiers or denominator counts.
|
|
280
319
|
|
|
281
|
-
Use them when you want the agent to find likely service-boundary flows faster; ignore them
|
|
320
|
+
Use them when you want the agent to find likely service-boundary flows faster; ignore them for a deterministic-only report.
|
|
282
321
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
## How it works
|
|
286
|
-
|
|
287
|
-
OrangePro separates **analysis** (what your code does) from **proof** (whether tests actually verify it).
|
|
288
|
-
|
|
289
|
-
```
|
|
290
|
-
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
|
291
|
-
│ Your Code │ ──► │ Knowledge │ ──► │ Evidence │
|
|
292
|
-
│ (any lang) │ │ Graph │ │ Tiers │
|
|
293
|
-
└─────────────┘ └──────────────┘ └─────────────┘
|
|
294
|
-
│
|
|
295
|
-
┌──────┴──────┐
|
|
296
|
-
▼ ▼
|
|
297
|
-
┌───────────┐ ┌──────────┐
|
|
298
|
-
│ Gap Report│ │ Generate │
|
|
299
|
-
│ + Risks │ │ Tests │
|
|
300
|
-
└───────────┘ └──────────┘
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
| Phase | What happens | Needs a model key? |
|
|
304
|
-
|-------|-------------|-------------------|
|
|
305
|
-
| **Analyze** | AST walk → behaviors, flows, evidence tiers | No |
|
|
306
|
-
| **Score** | Graph readiness score (0–100) with reasons | No |
|
|
307
|
-
| **Generate** | Grounded tests for top gaps, per-behavior | Yes (BYOK) |
|
|
308
|
-
| **Prove** | Mutation-kill oracle confirms test actually breaks if behavior changes | No |
|
|
309
|
-
|
|
310
|
-
Reruns are cache-accelerated: unchanged files skip re-parsing, BYOK stages don't re-spend tokens on unchanged inputs, and proof certificates persist in a local ledger until the certified file changes. Upgrading the tool auto-invalidates caches.
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## Privacy
|
|
315
|
-
|
|
316
|
-
- **No stored source.** Reads code in-process. Never uploads to an OrangePro server.
|
|
317
|
-
- **No existing-source mutation.** Never edits existing source or test files. Writes metadata to `.orangepro/`; keyed auto-drive may write new, reviewable tests under `orangepro_generated/`.
|
|
318
|
-
- **Metadata-only exports.** File paths, names, hashes, scores — not raw source.
|
|
319
|
-
- **Your keys stay yours.** Read from env at call time, never persisted.
|
|
320
|
-
- **BYOK is direct.** When AI lanes are enabled, grounded code context is sent directly to the model provider you configure; OrangePro's hosted service is not in that path.
|
|
322
|
+
</details>
|
|
321
323
|
|
|
322
324
|
---
|
|
323
325
|
|
|
@@ -326,25 +328,26 @@ Reruns are cache-accelerated: unchanged files skip re-parsing, BYOK stages don't
|
|
|
326
328
|
This repo is the free local tool. The [OrangePro platform](https://orangepro.ai) adds:
|
|
327
329
|
|
|
328
330
|
- Persistent knowledge graph across PRs and repos
|
|
329
|
-
-
|
|
330
|
-
- PR/CI policy gates over Dynamically Proven, Runtime-covered, and risk deltas
|
|
331
|
+
- PR/CI policy gates over evidence tiers and risk deltas
|
|
331
332
|
- Jira / Confluence / TestRail / OpenAPI enrichment
|
|
332
333
|
- Cross-repo intelligence and recurring-flow memory
|
|
333
334
|
- Production incident correlation and regression targeting
|
|
334
|
-
-
|
|
335
|
+
- Team dashboards and test lifecycle management
|
|
335
336
|
|
|
336
337
|
---
|
|
337
338
|
|
|
338
339
|
## Contributing
|
|
339
340
|
|
|
340
341
|
```bash
|
|
341
|
-
|
|
342
|
-
npm
|
|
343
|
-
npm
|
|
342
|
+
git clone https://github.com/OrangeproAI/orangepro-mcp.git
|
|
343
|
+
cd orangepro-mcp && npm ci && npm run build
|
|
344
|
+
npm test
|
|
344
345
|
```
|
|
345
346
|
|
|
346
|
-
|
|
347
|
+
PRs welcome. Please open an issue first for large changes.
|
|
347
348
|
|
|
348
|
-
|
|
349
|
+
---
|
|
349
350
|
|
|
350
|
-
|
|
351
|
+
<p align="center">
|
|
352
|
+
MIT License · <a href="https://orangepro.ai">orangepro.ai</a>
|
|
353
|
+
</p>
|
|
@@ -26,7 +26,10 @@ const SYMBOL_EXCERPT_CONTEXT_LINES = 3;
|
|
|
26
26
|
const MAX_EXCERPT_CHARS = 8000;
|
|
27
27
|
const MAX_TARGET_TYPE_EXCERPT_CHARS = 5000;
|
|
28
28
|
const STATIC_CHECK_TIMEOUT_MS = 3000;
|
|
29
|
-
|
|
29
|
+
// Large Go monorepos can spend tens of seconds populating a cold module cache
|
|
30
|
+
// before the target package is compiled. Keep the check authoritative instead
|
|
31
|
+
// of downgrading valid generated tests while dependencies are still downloading.
|
|
32
|
+
const GO_COMPILE_CHECK_TIMEOUT_MS = 120000;
|
|
30
33
|
/** A source ref that names a test file (used to place a generated test next to it). */
|
|
31
34
|
const TEST_REF_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|((^|\/)test\.[cm]?[jt]sx?$)|(_test\.[a-z]+$)|(_spec\.[a-z]+$)|((^|\/)test_[^/]+\.[a-z]+$)/i;
|
|
32
35
|
function areaOf(relPath) {
|
|
@@ -518,6 +521,9 @@ export function gatherContext(graph, behavior, framework, fileReader) {
|
|
|
518
521
|
// Subject imports are TS/JS lines — feeding them to a pytest/go target would
|
|
519
522
|
// produce an unparseable test in the other language.
|
|
520
523
|
const fwLower = framework.toLowerCase();
|
|
524
|
+
const goContext = fwLower.includes("go")
|
|
525
|
+
? goGenerationImportContext(graph.workspace.root, [...relatedFiles, ...testFiles], fileReader)
|
|
526
|
+
: { go_import_paths: [], go_module_paths: [], go_target_package_paths: [] };
|
|
521
527
|
const subjectImports = fwLower.includes("pytest") || fwLower.includes("python") || fwLower.includes("go") || fwLower.includes("junit") || fwLower.includes("java")
|
|
522
528
|
? []
|
|
523
529
|
: subjectImportsFor(graph, testFiles);
|
|
@@ -561,6 +567,9 @@ export function gatherContext(graph, behavior, framework, fileReader) {
|
|
|
561
567
|
// MISSING, never a re-derivation of a test that already exists.
|
|
562
568
|
existing_tests: examples.slice(0, 10),
|
|
563
569
|
subject_imports: subjectImports,
|
|
570
|
+
...(goContext.go_import_paths.length ? { go_import_paths: goContext.go_import_paths } : {}),
|
|
571
|
+
...(goContext.go_module_paths.length ? { go_module_paths: goContext.go_module_paths } : {}),
|
|
572
|
+
...(goContext.go_target_package_paths.length ? { go_target_package_paths: goContext.go_target_package_paths } : {}),
|
|
564
573
|
...(flowChain ? { flow_chain: flowChain } : {})
|
|
565
574
|
};
|
|
566
575
|
// entity_ids are graph external_ids: the behavior plus related file paths. A
|
|
@@ -1578,10 +1587,6 @@ function goStaticIssue(body) {
|
|
|
1578
1587
|
if (!/\bfunc\s+Test[A-Za-z0-9_]*\s*\(\s*t\s+\*testing\.T\s*\)/m.test(body)) {
|
|
1579
1588
|
return "Go test is missing a func Test...(t *testing.T) entrypoint.";
|
|
1580
1589
|
}
|
|
1581
|
-
const external = imports.filter((spec) => spec.split("/")[0].includes("."));
|
|
1582
|
-
if (external.length) {
|
|
1583
|
-
return `Go test imports module-path package(s) ${external.join(", ")}; OrangePro cannot verify module imports resolve from the generated file. Prefer same-package or stdlib-only code.`;
|
|
1584
|
-
}
|
|
1585
1590
|
if (!commandAvailable("gofmt"))
|
|
1586
1591
|
return "gofmt not found; cannot verify Go syntax.";
|
|
1587
1592
|
const { dir, file } = writeTempStaticFile("go", body);
|
|
@@ -1611,26 +1616,58 @@ function findGoModuleRoot(startDir, workspaceRoot) {
|
|
|
1611
1616
|
}
|
|
1612
1617
|
return null;
|
|
1613
1618
|
}
|
|
1614
|
-
function
|
|
1619
|
+
function unusedImportLineNumbers(output, tempRel) {
|
|
1620
|
+
const escaped = tempRel.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1621
|
+
const diagnostic = new RegExp(`(?:^|\\n)(?:\\./)?${escaped}:(\\d+):\\d+:\\s+[^\\n]*\\bimported\\b[^\\n]*\\bnot used\\b`, "g");
|
|
1622
|
+
return new Set([...output.matchAll(diagnostic)].map((match) => Number(match[1])).filter(Number.isFinite));
|
|
1623
|
+
}
|
|
1624
|
+
function removeLines(body, lineNumbers) {
|
|
1625
|
+
if (!lineNumbers.size)
|
|
1626
|
+
return body;
|
|
1627
|
+
return body
|
|
1628
|
+
.split(/\r?\n/)
|
|
1629
|
+
.filter((_line, index) => !lineNumbers.has(index + 1))
|
|
1630
|
+
.join("\n");
|
|
1631
|
+
}
|
|
1632
|
+
function goCompile(body, workspaceRoot, relatedFiles) {
|
|
1633
|
+
const externalImports = goImportSpecs(body).filter((spec) => spec.split("/")[0].includes("."));
|
|
1634
|
+
const unverifiedImportIssue = externalImports.length
|
|
1635
|
+
? `Go test imports module-path package(s) ${externalImports.join(", ")}, but OrangePro could not run the target-package compile check to verify they resolve.`
|
|
1636
|
+
: null;
|
|
1615
1637
|
const sourceFile = relatedFiles.find((rel) => /\.go$/i.test(rel) && !TEST_REF_RE.test(rel));
|
|
1616
1638
|
if (!sourceFile)
|
|
1617
|
-
return
|
|
1639
|
+
return { body, issue: unverifiedImportIssue };
|
|
1618
1640
|
const packageDir = path.resolve(workspaceRoot, path.dirname(sourceFile));
|
|
1619
1641
|
if (!existsSync(packageDir) || !findGoModuleRoot(packageDir, workspaceRoot))
|
|
1620
|
-
return
|
|
1642
|
+
return { body, issue: unverifiedImportIssue };
|
|
1621
1643
|
if (!commandAvailable("go"))
|
|
1622
|
-
return "go not found; cannot verify generated Go test compiles.";
|
|
1644
|
+
return { body, issue: "go not found; cannot verify generated Go test compiles." };
|
|
1623
1645
|
const tempRel = `orangepro_compile_${process.pid}_${shortHash(body)}_test.go`;
|
|
1624
1646
|
const tempFile = path.join(packageDir, tempRel);
|
|
1647
|
+
let checkedBody = body;
|
|
1625
1648
|
try {
|
|
1626
|
-
writeFileSync(tempFile,
|
|
1649
|
+
writeFileSync(tempFile, checkedBody);
|
|
1627
1650
|
execFileSync("go", ["test", "-run", "^$", "."], { cwd: packageDir, stdio: "pipe", timeout: GO_COMPILE_CHECK_TIMEOUT_MS });
|
|
1628
|
-
return null;
|
|
1651
|
+
return { body: checkedBody, issue: null };
|
|
1629
1652
|
}
|
|
1630
1653
|
catch (e) {
|
|
1631
1654
|
const err = e;
|
|
1632
1655
|
const output = `${err.stdout?.toString() ?? ""}${err.stderr?.toString() ?? ""}`;
|
|
1633
|
-
|
|
1656
|
+
const withoutUnusedImports = removeLines(checkedBody, unusedImportLineNumbers(output, tempRel));
|
|
1657
|
+
if (withoutUnusedImports !== checkedBody) {
|
|
1658
|
+
checkedBody = withoutUnusedImports;
|
|
1659
|
+
try {
|
|
1660
|
+
writeFileSync(tempFile, checkedBody);
|
|
1661
|
+
execFileSync("go", ["test", "-run", "^$", "."], { cwd: packageDir, stdio: "pipe", timeout: GO_COMPILE_CHECK_TIMEOUT_MS });
|
|
1662
|
+
return { body: checkedBody, issue: null };
|
|
1663
|
+
}
|
|
1664
|
+
catch (retryError) {
|
|
1665
|
+
const retry = retryError;
|
|
1666
|
+
const retryOutput = `${retry.stdout?.toString() ?? ""}${retry.stderr?.toString() ?? ""}`;
|
|
1667
|
+
return { body: checkedBody, issue: `Go compile check failed: ${shortStaticDiag(retryOutput || retry.message || "unknown error")}` };
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return { body: checkedBody, issue: `Go compile check failed: ${shortStaticDiag(output || err.message || "unknown error")}` };
|
|
1634
1671
|
}
|
|
1635
1672
|
finally {
|
|
1636
1673
|
rmSync(tempFile, { force: true });
|
|
@@ -1659,6 +1696,37 @@ function goImportSpecs(body) {
|
|
|
1659
1696
|
}
|
|
1660
1697
|
return specs;
|
|
1661
1698
|
}
|
|
1699
|
+
function goGenerationImportContext(workspaceRoot, files, fileReader) {
|
|
1700
|
+
const goFiles = files.filter((file) => /\.go$/i.test(file));
|
|
1701
|
+
const moduleRoots = dedupe(goFiles
|
|
1702
|
+
.map((file) => findGoModuleRoot(path.resolve(workspaceRoot, path.dirname(file)), workspaceRoot))
|
|
1703
|
+
.filter((root) => Boolean(root)));
|
|
1704
|
+
const modulePathByRoot = new Map(moduleRoots.map((root) => {
|
|
1705
|
+
try {
|
|
1706
|
+
return [root, readFileSync(path.join(root, "go.mod"), "utf8").match(/^\s*module\s+(\S+)/m)?.[1] ?? ""];
|
|
1707
|
+
}
|
|
1708
|
+
catch {
|
|
1709
|
+
return [root, ""];
|
|
1710
|
+
}
|
|
1711
|
+
}));
|
|
1712
|
+
const go_module_paths = dedupe([...modulePathByRoot.values()].filter(Boolean));
|
|
1713
|
+
const go_target_package_paths = dedupe(goFiles
|
|
1714
|
+
.map((file) => {
|
|
1715
|
+
const sourceDir = path.resolve(workspaceRoot, path.dirname(file));
|
|
1716
|
+
const moduleRoot = findGoModuleRoot(sourceDir, workspaceRoot);
|
|
1717
|
+
const modulePath = moduleRoot ? modulePathByRoot.get(moduleRoot) : "";
|
|
1718
|
+
if (!moduleRoot || !modulePath)
|
|
1719
|
+
return "";
|
|
1720
|
+
const packageDir = path.relative(moduleRoot, sourceDir).split(path.sep).join("/");
|
|
1721
|
+
return packageDir && packageDir !== "." ? `${modulePath}/${packageDir}` : modulePath;
|
|
1722
|
+
})
|
|
1723
|
+
.filter(Boolean));
|
|
1724
|
+
const targetPaths = new Set(go_target_package_paths);
|
|
1725
|
+
const go_import_paths = dedupe(goFiles
|
|
1726
|
+
.flatMap((file) => goImportSpecs(fileReader(file) ?? ""))
|
|
1727
|
+
.filter((spec) => spec.split("/")[0].includes(".") && !targetPaths.has(spec))).slice(0, 40);
|
|
1728
|
+
return { go_import_paths, go_module_paths, go_target_package_paths };
|
|
1729
|
+
}
|
|
1662
1730
|
function hasBalancedBraces(body) {
|
|
1663
1731
|
let depth = 0;
|
|
1664
1732
|
let stringQuote = null;
|
|
@@ -2176,8 +2244,13 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2176
2244
|
body = applyPythonSrcLayoutImports(body, relatedFiles);
|
|
2177
2245
|
}
|
|
2178
2246
|
body = ensureFrameworkScaffold(body, framework);
|
|
2247
|
+
let compileIssue = null;
|
|
2248
|
+
if (framework.toLowerCase().includes("go")) {
|
|
2249
|
+
const checked = goCompile(body, graph.workspace.root, relatedFiles);
|
|
2250
|
+
body = checked.body;
|
|
2251
|
+
compileIssue = checked.issue;
|
|
2252
|
+
}
|
|
2179
2253
|
const staticIssue = staticFormatIssue(body, framework);
|
|
2180
|
-
const compileIssue = framework.toLowerCase().includes("go") ? goCompileIssue(body, graph.workspace.root, relatedFiles) : null;
|
|
2181
2254
|
const runnable = hasAssertion(body, framework) && !staticIssue && !compileIssue;
|
|
2182
2255
|
generated.push({
|
|
2183
2256
|
id: `${run_id}-t${generated.length + 1}`,
|
|
@@ -2433,7 +2506,12 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2433
2506
|
}
|
|
2434
2507
|
}
|
|
2435
2508
|
const importErrors = isResolverFramework(framework) ? unresolvedLocalImports(body, genTestAbs, graph.workspace.root, declaredDeps) : [];
|
|
2436
|
-
|
|
2509
|
+
let compileIssue = null;
|
|
2510
|
+
if (framework.toLowerCase().includes("go")) {
|
|
2511
|
+
const checked = goCompile(body, graph.workspace.root, relatedFiles);
|
|
2512
|
+
body = checked.body;
|
|
2513
|
+
compileIssue = checked.issue;
|
|
2514
|
+
}
|
|
2437
2515
|
const runnable = isRunnable(body, framework, import_provenance, importErrors) && !compileIssue;
|
|
2438
2516
|
if (!runnable) {
|
|
2439
2517
|
const reason = unresolved_reason ?? compileIssue ?? runnableFailureReason(body, framework, import_provenance, importErrors, declaredDeps);
|
|
@@ -2651,7 +2729,12 @@ export async function generateTests(graph, opts, provider, fileReader, clock = s
|
|
|
2651
2729
|
// from where the test will live. A test whose own import won't load is never
|
|
2652
2730
|
// marked runnable.
|
|
2653
2731
|
const importErrors = isResolverFramework(framework) ? unresolvedLocalImports(body, genTestAbs, graph.workspace.root, declaredDeps) : [];
|
|
2654
|
-
|
|
2732
|
+
let compileIssue = null;
|
|
2733
|
+
if (framework.toLowerCase().includes("go")) {
|
|
2734
|
+
const checked = goCompile(body, graph.workspace.root, relatedFiles);
|
|
2735
|
+
body = checked.body;
|
|
2736
|
+
compileIssue = checked.issue;
|
|
2737
|
+
}
|
|
2655
2738
|
const runnable = isRunnable(body, framework, import_provenance, importErrors) && !compileIssue;
|
|
2656
2739
|
if (!runnable && !unresolved_reason) {
|
|
2657
2740
|
unresolved_reason = compileIssue ?? runnableFailureReason(body, framework, import_provenance, importErrors, declaredDeps);
|
|
@@ -32,8 +32,10 @@ export function buildSystemPrompt() {
|
|
|
32
32
|
"- Framework format rules:",
|
|
33
33
|
" - pytest: output a valid Python file with pytest-style `def test_...` functions and `assert` statements.",
|
|
34
34
|
" - Go: output a same-package `_test.go` body. Include `package <same package>`, `import \"testing\"`,",
|
|
35
|
-
" and `func Test...(t *testing.T)`.
|
|
36
|
-
"
|
|
35
|
+
" and `func Test...(t *testing.T)`. Do not import packages you do not use. Prefer stdlib and exact",
|
|
36
|
+
" OBSERVED GO IMPORT PATHS; never derive or invent a package subpath from a module name.",
|
|
37
|
+
" Do not qualify lowercase (unexported) identifiers from imported packages. Bare same-package",
|
|
38
|
+
" identifiers may be unexported because the generated test is rewritten into the target package.",
|
|
37
39
|
" - Java/JUnit: output a complete `.java` file, not a method fragment. Include a `class <Name>Test { ... }`,",
|
|
38
40
|
" the requested JUnit version's `@Test` import, and a JUnit assertion.",
|
|
39
41
|
" - TS/JS: output valid framework code for the named framework and use complete imports.",
|
|
@@ -78,6 +80,21 @@ export function buildGroundedUserPrompt(ctx, bucket) {
|
|
|
78
80
|
for (const imp of ctx.subject_imports)
|
|
79
81
|
lines.push(imp);
|
|
80
82
|
}
|
|
83
|
+
if (ctx.go_module_paths?.length) {
|
|
84
|
+
lines.push("GO MODULE IDENTITIES (module roots only — never guess package subpaths from these):");
|
|
85
|
+
for (const modulePath of ctx.go_module_paths)
|
|
86
|
+
lines.push(`- ${modulePath}`);
|
|
87
|
+
}
|
|
88
|
+
if (ctx.go_import_paths?.length) {
|
|
89
|
+
lines.push("OBSERVED GO IMPORT PATHS (exact repo-evidenced package paths; prefer these for non-stdlib imports):");
|
|
90
|
+
for (const importPath of ctx.go_import_paths)
|
|
91
|
+
lines.push(`- ${importPath}`);
|
|
92
|
+
}
|
|
93
|
+
if (ctx.go_target_package_paths?.length) {
|
|
94
|
+
lines.push("TARGET GO PACKAGE PATHS (do not import these; the test is rewritten into the same package):");
|
|
95
|
+
for (const targetPath of ctx.go_target_package_paths)
|
|
96
|
+
lines.push(`- ${targetPath}`);
|
|
97
|
+
}
|
|
81
98
|
if (ctx.source_excerpts.length) {
|
|
82
99
|
lines.push("SOURCE EXCERPTS:");
|
|
83
100
|
lines.push("Use these for understanding only; do not copy their lines verbatim into the test body.");
|
|
@@ -97,8 +114,10 @@ export function buildGroundedUserPrompt(ctx, bucket) {
|
|
|
97
114
|
}
|
|
98
115
|
else if (fw.includes("go")) {
|
|
99
116
|
lines.push("- Emit same-package Go test code only: `package ...`, `import \"testing\"`, and `func Test...(t *testing.T)`.");
|
|
100
|
-
lines.push("- Do not import
|
|
101
|
-
lines.push("-
|
|
117
|
+
lines.push("- Do not import packages you do not use.");
|
|
118
|
+
lines.push("- Prefer stdlib and exact OBSERVED GO IMPORT PATHS. Never invent a package path or derive a subpath from a GO MODULE IDENTITY.");
|
|
119
|
+
lines.push("- Never import a TARGET GO PACKAGE PATH; call its identifiers directly because this is a same-package test.");
|
|
120
|
+
lines.push("- Do not qualify lowercase (unexported) identifiers from imported packages; bare same-package identifiers may be unexported.");
|
|
102
121
|
}
|
|
103
122
|
else if (fw.includes("junit") || fw.includes("java")) {
|
|
104
123
|
if (fw.includes("junit4")) {
|
|
@@ -33,7 +33,15 @@ export function getFrameworkRules(framework) {
|
|
|
33
33
|
return "Python: `def test_...` with `assert`. Use `# Concern:` and `# Technique:` comments.";
|
|
34
34
|
}
|
|
35
35
|
if (fw.includes("go")) {
|
|
36
|
-
return
|
|
36
|
+
return [
|
|
37
|
+
"Go: same-package `_test.go` file. `func Test...(t *testing.T)`.",
|
|
38
|
+
"MUST start with `package <name>` matching the package under test.",
|
|
39
|
+
"MUST import `\"testing\"`; do not import any package you do not use.",
|
|
40
|
+
"Prefer stdlib and exact OBSERVED GO IMPORT PATHS. Never invent a package path or derive a subpath from a GO MODULE IDENTITY.",
|
|
41
|
+
"Never import a TARGET GO PACKAGE PATH; call its identifiers directly because this is a same-package test.",
|
|
42
|
+
"Do not qualify lowercase (unexported) identifiers from imported packages; bare same-package identifiers may be unexported.",
|
|
43
|
+
"Prefer stdlib `testing` over testify unless testify appears in OBSERVED GO IMPORT PATHS."
|
|
44
|
+
].join(" ");
|
|
37
45
|
}
|
|
38
46
|
if (fw.includes("junit4") || fw.includes("java4")) {
|
|
39
47
|
return "JUnit 4: `import org.junit.Test;` + `import static org.junit.Assert.*;`. Complete .java file.";
|
|
@@ -179,6 +187,21 @@ export function buildBatchGenerationUserPromptV5(ctx) {
|
|
|
179
187
|
lines.push(`ACTORS: ${ctx.actors.join(", ")}`);
|
|
180
188
|
lines.push(`FRAMEWORK: ${ctx.framework} | LAYER: ${ctx.test_layer}`);
|
|
181
189
|
lines.push(`FRAMEWORK RULES: ${getFrameworkRules(ctx.framework)}`);
|
|
190
|
+
if (ctx.go_module_paths?.length) {
|
|
191
|
+
lines.push("GO MODULE IDENTITIES (module roots only — never guess package subpaths from these):");
|
|
192
|
+
for (const modulePath of ctx.go_module_paths)
|
|
193
|
+
lines.push(` ${modulePath}`);
|
|
194
|
+
}
|
|
195
|
+
if (ctx.go_import_paths?.length) {
|
|
196
|
+
lines.push("OBSERVED GO IMPORT PATHS (exact repo-evidenced package paths):");
|
|
197
|
+
for (const importPath of ctx.go_import_paths)
|
|
198
|
+
lines.push(` ${importPath}`);
|
|
199
|
+
}
|
|
200
|
+
if (ctx.go_target_package_paths?.length) {
|
|
201
|
+
lines.push("TARGET GO PACKAGE PATHS (do not import; this is a same-package test):");
|
|
202
|
+
for (const targetPath of ctx.go_target_package_paths)
|
|
203
|
+
lines.push(` ${targetPath}`);
|
|
204
|
+
}
|
|
182
205
|
lines.push("");
|
|
183
206
|
if (ctx.flow_chain?.length) {
|
|
184
207
|
lines.push("FLOW CHAIN:");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangepro/orangepro-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.30",
|
|
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",
|