@orangepro/orangepro-mcp 0.1.0 → 0.2.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/LICENSE +1 -1
- package/README.md +195 -251
- package/dist/local/analyze/analyzer.js +62 -17
- package/dist/local/analyze/treeSitter/engine.js +185 -22
- package/dist/local/autoProve.js +433 -38
- 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 +454 -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 +2 -2
- package/docs/agents/opencode.md +2 -2
- package/docs/agents/vscode.md +2 -2
- 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 +245 -0
- package/scripts/spikes/python-mutate.py +89 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//go:build ignore
|
|
2
|
+
|
|
3
|
+
// go-mutate.go — AST-based body replacer for the Go dynamic-proof spike (G-1).
|
|
4
|
+
//
|
|
5
|
+
// Locates ONE free function `func Name(params) rets { ... }` by exact name and
|
|
6
|
+
// replaces its BODY with a signature-derived sentinel, then writes the mutated
|
|
7
|
+
// file. It is a spike helper only: it writes no product artifacts and is not
|
|
8
|
+
// wired into prove/RTM/mint. G-1 scope is FREE FUNCTIONS ONLY — methods
|
|
9
|
+
// (`func (r Recv) M()`) are recognized and refused as out-of-scope (G-2).
|
|
10
|
+
//
|
|
11
|
+
// Modes:
|
|
12
|
+
// sentinel — replace body with a type-compatible, deliberately-wrong value
|
|
13
|
+
// (zero values). Zero-valued returns can only ever cause a FALSE
|
|
14
|
+
// SURVIVE, never a false Proven, so the trust bias is safe.
|
|
15
|
+
// equivalent — leave the body semantically unchanged (re-print identical
|
|
16
|
+
// statements) so a value-only test still passes -> the
|
|
17
|
+
// orchestrator classifies it associated_survived.
|
|
18
|
+
//
|
|
19
|
+
// Exit codes (distinct, so the Node orchestrator can classify precisely):
|
|
20
|
+
// 0 ok, mutated file written
|
|
21
|
+
// 2 usage / IO / parse error
|
|
22
|
+
// 3 ambiguous: more than one free function with that name
|
|
23
|
+
// 4 not found: no free function with that name
|
|
24
|
+
// 5 out of scope: a METHOD with that name exists (G-2, refused in G-1)
|
|
25
|
+
// 6 not mutable: the function has no return values (no signature-derived
|
|
26
|
+
// sentinel is possible) -> fail closed, never mutated
|
|
27
|
+
package main
|
|
28
|
+
|
|
29
|
+
import (
|
|
30
|
+
"bytes"
|
|
31
|
+
"flag"
|
|
32
|
+
"fmt"
|
|
33
|
+
"go/ast"
|
|
34
|
+
"go/parser"
|
|
35
|
+
"go/printer"
|
|
36
|
+
"go/token"
|
|
37
|
+
"os"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// fail prints a stable, machine-readable marker plus a human message and exits
|
|
41
|
+
// with the given code. The marker matters because callers run this via
|
|
42
|
+
// `go run`, which collapses any non-zero child status to 1 — so the Node
|
|
43
|
+
// orchestrator classifies on the MUTATE_ERROR:<code> line, not the exit code.
|
|
44
|
+
// The distinct os.Exit code is still set for direct (compiled) callers.
|
|
45
|
+
func fail(code int, format string, args ...any) {
|
|
46
|
+
fmt.Fprintf(os.Stderr, "MUTATE_ERROR:%d\n", code)
|
|
47
|
+
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
|
48
|
+
os.Exit(code)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func main() {
|
|
52
|
+
file := flag.String("file", "", "path to the Go source file to mutate")
|
|
53
|
+
fn := flag.String("func", "", "exact name of the free function to mutate")
|
|
54
|
+
out := flag.String("out", "", "path to write the mutated file (defaults to --file)")
|
|
55
|
+
mode := flag.String("mode", "sentinel", "sentinel | equivalent")
|
|
56
|
+
flag.Parse()
|
|
57
|
+
|
|
58
|
+
if *file == "" || *fn == "" {
|
|
59
|
+
fail(2, "usage: go run go-mutate.go --file <path> --func <name> [--out <path>] [--mode sentinel|equivalent]")
|
|
60
|
+
}
|
|
61
|
+
if *mode != "sentinel" && *mode != "equivalent" {
|
|
62
|
+
fail(2, "--mode must be sentinel or equivalent")
|
|
63
|
+
}
|
|
64
|
+
dst := *out
|
|
65
|
+
if dst == "" {
|
|
66
|
+
dst = *file
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fset := token.NewFileSet()
|
|
70
|
+
astFile, err := parser.ParseFile(fset, *file, nil, parser.ParseComments)
|
|
71
|
+
if err != nil {
|
|
72
|
+
fail(2, "parse error: %v", err)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
var freeMatches []*ast.FuncDecl
|
|
76
|
+
methodMatch := false
|
|
77
|
+
for _, decl := range astFile.Decls {
|
|
78
|
+
fd, ok := decl.(*ast.FuncDecl)
|
|
79
|
+
if !ok || fd.Name == nil || fd.Name.Name != *fn {
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
if fd.Recv != nil { // method -> out of scope for G-1
|
|
83
|
+
methodMatch = true
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
freeMatches = append(freeMatches, fd)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if len(freeMatches) > 1 {
|
|
90
|
+
fail(3, "ambiguous: %d free functions named %q", len(freeMatches), *fn)
|
|
91
|
+
}
|
|
92
|
+
if len(freeMatches) == 0 {
|
|
93
|
+
if methodMatch {
|
|
94
|
+
fail(5, "out of scope: %q is a method (G-2); G-1 handles free functions only", *fn)
|
|
95
|
+
}
|
|
96
|
+
fail(4, "not found: no free function named %q", *fn)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
target := freeMatches[0]
|
|
100
|
+
results := target.Type.Results
|
|
101
|
+
if results == nil || len(results.List) == 0 {
|
|
102
|
+
fail(6, "not mutable: %q has no return values; no signature-derived sentinel is possible", *fn)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if *mode == "sentinel" {
|
|
106
|
+
target.Body = sentinelBody(results)
|
|
107
|
+
}
|
|
108
|
+
// equivalent mode: leave target.Body untouched (semantically identical).
|
|
109
|
+
|
|
110
|
+
var buf bytes.Buffer
|
|
111
|
+
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
|
|
112
|
+
if err := cfg.Fprint(&buf, fset, astFile); err != nil {
|
|
113
|
+
fail(2, "print error: %v", err)
|
|
114
|
+
}
|
|
115
|
+
if err := os.WriteFile(dst, buf.Bytes(), 0o644); err != nil {
|
|
116
|
+
fail(2, "write error: %v", err)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// sentinelBody builds `{ return <zero>, <zero>, ... }` matching the function's
|
|
121
|
+
// result signature so the mutant COMPILES. Zero values are type-derived and
|
|
122
|
+
// deliberately wrong for any function whose real return is non-zero; when a
|
|
123
|
+
// function legitimately returns a zero value the sentinel merely SURVIVES
|
|
124
|
+
// (safe: never a false Proven).
|
|
125
|
+
func sentinelBody(results *ast.FieldList) *ast.BlockStmt {
|
|
126
|
+
var exprs []ast.Expr
|
|
127
|
+
for _, field := range results.List {
|
|
128
|
+
n := len(field.Names)
|
|
129
|
+
if n == 0 {
|
|
130
|
+
n = 1 // an unnamed result still contributes one value
|
|
131
|
+
}
|
|
132
|
+
for i := 0; i < n; i++ {
|
|
133
|
+
exprs = append(exprs, zeroValue(field.Type))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return &ast.BlockStmt{
|
|
137
|
+
List: []ast.Stmt{
|
|
138
|
+
&ast.ReturnStmt{Results: exprs},
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// zeroValue returns an expression that is the zero value of the given type.
|
|
144
|
+
// It covers the common cases; anything it cannot name concretely falls back to
|
|
145
|
+
// a type-conversion of nil-like zero via a composite/`*new(T)` form that always
|
|
146
|
+
// compiles.
|
|
147
|
+
func zeroValue(t ast.Expr) ast.Expr {
|
|
148
|
+
switch tt := t.(type) {
|
|
149
|
+
case *ast.Ident:
|
|
150
|
+
switch tt.Name {
|
|
151
|
+
case "int", "int8", "int16", "int32", "int64",
|
|
152
|
+
"uint", "uint8", "uint16", "uint32", "uint64", "uintptr",
|
|
153
|
+
"byte", "rune", "float32", "float64", "complex64", "complex128":
|
|
154
|
+
return &ast.BasicLit{Kind: token.INT, Value: "0"}
|
|
155
|
+
case "string":
|
|
156
|
+
return &ast.BasicLit{Kind: token.STRING, Value: `""`}
|
|
157
|
+
case "bool":
|
|
158
|
+
return ast.NewIdent("false")
|
|
159
|
+
case "error", "any":
|
|
160
|
+
return ast.NewIdent("nil")
|
|
161
|
+
default:
|
|
162
|
+
// A named type (struct/interface alias). *new(T) is the universal
|
|
163
|
+
// zero value that always compiles.
|
|
164
|
+
return newZero(tt)
|
|
165
|
+
}
|
|
166
|
+
case *ast.StarExpr, *ast.ArrayType, *ast.MapType, *ast.ChanType,
|
|
167
|
+
*ast.FuncType, *ast.InterfaceType:
|
|
168
|
+
return ast.NewIdent("nil")
|
|
169
|
+
default:
|
|
170
|
+
return newZero(t)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// newZero produces `*new(T)` — a universal, always-compiling zero value.
|
|
175
|
+
func newZero(t ast.Expr) ast.Expr {
|
|
176
|
+
return &ast.StarExpr{
|
|
177
|
+
X: &ast.CallExpr{
|
|
178
|
+
Fun: ast.NewIdent("new"),
|
|
179
|
+
Args: []ast.Expr{t},
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
}
|