Skip to content

Commit f3b4dd0

Browse files
Copilotmrjf
andauthored
Resolve merge conflicts with main
Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
2 parents 27d1868 + 1a7b2b0 commit f3b4dd0

18 files changed

Lines changed: 682 additions & 150 deletions

.crane/scripts/score.go

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//go:build ignore
22

3-
// score.go -- deletion-grade migration scoring script for the APM CLI Python-to-Go migration.
3+
// score.go -- deletion-grade migration scoring for the APM CLI Python-to-Go migration.
44
// Usage: go test -json ./... | go run .crane/scripts/score.go
55
// Outputs JSON that separates progress metrics from cutover-readiness gates.
66
package main
@@ -76,6 +76,12 @@ type ProgressMetrics struct {
7676
PerfRatio float64 `json:"perf_ratio"`
7777
}
7878

79+
type GateResult struct {
80+
Name string `json:"name"`
81+
Passing bool `json:"passing"`
82+
Reason string `json:"reason,omitempty"`
83+
}
84+
7985
type Score struct {
8086
MigrationScore float64 `json:"migration_score"`
8187
Progress float64 `json:"progress"`
@@ -97,6 +103,7 @@ type Score struct {
97103
SourceTestsPassing int `json:"source_tests_passing"`
98104
TargetTestsPassing int `json:"target_tests_passing"`
99105
PerfRatio float64 `json:"perf_ratio"`
106+
Gates []GateResult `json:"gates"`
100107
}
101108

102109
func main() {
@@ -118,15 +125,15 @@ type scanInput interface {
118125

119126
func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
120127
scanner := bufio.NewScanner(input)
128+
scanner.Buffer(make([]byte, 4*1024*1024), 4*1024*1024)
121129

122130
var parityPassing, parityTotal, targetPassing, targetTotal int
123131
eventsSeen := 0
124-
terminalPackages := 0
125132
goTestsFailed := false
126133
running := map[string]bool{}
127134
passed := map[string]bool{}
135+
failed := map[string]bool{}
128136
knownExceptions := knownExceptionsFromEnv(getenv("APM_KNOWN_EXCEPTIONS"))
129-
knownExceptionsSeen := getenv("APM_KNOWN_EXCEPTIONS") != ""
130137
pythonReference := BoolGate{}
131138
pythonTests := BoolGate{Seen: getenv("APM_PYTHON_TESTS") != "", Passed: getenv("APM_PYTHON_TESTS") == "pass"}
132139
benchmarks := BoolGate{Seen: getenv("APM_BENCHMARKS") != "", Passed: getenv("APM_BENCHMARKS") == "pass"}
@@ -155,7 +162,6 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
155162
case "state_diff":
156163
stateDiff = RatioGate{Seen: true, Passing: gate.Passing, Total: gate.Total}
157164
case "known_exceptions":
158-
knownExceptionsSeen = true
159165
knownExceptions = gate.Count
160166
case "python_tests":
161167
pythonTests = BoolGate{Seen: true, Passed: gate.Passed}
@@ -178,9 +184,6 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
178184
}
179185

180186
if ev.Test == "" {
181-
if isTargetPackage(ev.Package) && (ev.Action == "pass" || ev.Action == "fail") {
182-
terminalPackages++
183-
}
184187
if isTargetPackage(ev.Package) && ev.Action == "fail" {
185188
goTestsFailed = true
186189
}
@@ -194,11 +197,14 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
194197
case "pass":
195198
passed[ev.Test] = true
196199
delete(running, key)
197-
case "fail", "skip":
200+
case "fail":
201+
failed[ev.Test] = true
198202
delete(running, key)
199-
if ev.Action == "fail" && isTargetPackage(ev.Package) {
203+
if isTargetPackage(ev.Package) {
200204
goTestsFailed = true
201205
}
206+
case "skip":
207+
delete(running, key)
202208
}
203209

204210
isParity := strings.Contains(ev.Test, "Parity") || strings.Contains(ev.Package, "parity")
@@ -223,27 +229,30 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
223229
if eventsSeen == 0 || targetTotal == 0 {
224230
return Score{}, fmt.Errorf("Go test event stream is empty or incomplete")
225231
}
226-
if terminalPackages == 0 && !allExplicitGatesSeen(pythonReference, pythonTests, benchmarks, surface, help, functional, stateDiff, knownExceptionsSeen) {
227-
return Score{}, fmt.Errorf("Go test event stream is incomplete: no package completion event")
228-
}
229232
if len(running) > 0 {
230233
return Score{}, fmt.Errorf("Go test event stream is incomplete: %d test(s) did not finish", len(running))
231234
}
232235

233236
if !pythonReference.Seen {
234-
pythonReference = BoolGate{Seen: true, Passed: pythonReferenceReady(getenv("APM_PYTHON_BIN"))}
237+
pythonReference = BoolGate{Seen: true, Passed: testPassed(passed, failed, "TestParityCompletionHardGate") || pythonReferenceReady(getenv("APM_PYTHON_BIN"))}
235238
}
236239
if !surface.Seen {
237-
surface = RatioGate{Seen: true, Passing: boolToInt(requiredGatePassed(passed, "TestParitySurfaceInventory", "TestParityCompletionCommandMatrix")), Total: 1}
240+
surface = inferredAnyRatioGate(passed, failed, "TestParityCompletionSurfaceParity", "TestParitySurfaceInventory")
238241
}
239242
if !help.Seen {
240-
help = RatioGate{Seen: true, Passing: boolToInt(requiredGatePassed(passed, "TestParityCompletionHelpIdentical")), Total: 1}
243+
help = inferredAllRatioGate(passed, failed, "TestParityCompletionCommandMatrix", "TestParityCompletionHelpIdentical")
241244
}
242245
if !functional.Seen {
243-
functional = RatioGate{Seen: true, Passing: boolToInt(requiredGatePassed(passed, "TestParityFunctionalContracts")), Total: 1}
246+
functional = inferredAnyRatioGate(passed, failed, "TestParityCompletionFunctionalContracts", "TestParityFunctionalContracts")
244247
}
245248
if !stateDiff.Seen {
246-
stateDiff = RatioGate{Seen: true, Passing: boolToInt(requiredGatePassed(passed, "TestParityStateDiffContracts")), Total: 1}
249+
stateDiff = inferredAnyRatioGate(passed, failed, "TestParityCompletionStateDiffContracts", "TestParityStateDiffContracts")
250+
}
251+
if !pythonTests.Seen {
252+
pythonTests = BoolGate{Seen: true, Passed: testPassed(passed, failed, "TestParityCompletionPythonSuite")}
253+
}
254+
if !benchmarks.Seen {
255+
benchmarks = BoolGate{Seen: true, Passed: testPassed(passed, failed, "TestParityCompletionBenchmarks")}
247256
}
248257

249258
goTestsPass := !goTestsFailed && targetTotal > 0 && targetPassing == targetTotal
@@ -274,7 +283,6 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
274283
gates.HelpParity == 1.0 &&
275284
gates.FunctionalContracts == 1.0 &&
276285
gates.StateDiffContracts == 1.0 &&
277-
knownExceptionsSeen &&
278286
gates.KnownExceptions == 0 &&
279287
gates.GoTests == "pass" &&
280288
gates.PythonTests == "pass" &&
@@ -287,7 +295,7 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
287295
if !cutoverReady && migrationScore >= 1.0 {
288296
migrationScore = 0.999
289297
}
290-
if cutoverReady {
298+
if cutoverReady && progress == 1.0 {
291299
migrationScore = 1.0
292300
}
293301

@@ -320,6 +328,7 @@ func computeScore(input scanInput, getenv getenvFunc) (Score, error) {
320328
SourceTestsPassing: metrics.SourceTestsPassing,
321329
TargetTestsPassing: metrics.TargetTestsPassing,
322330
PerfRatio: metrics.PerfRatio,
331+
Gates: gateResults(gates),
323332
}, nil
324333
}
325334

@@ -338,7 +347,12 @@ func pythonReferenceReady(bin string) bool {
338347
return info.Mode()&0o111 != 0
339348
}
340349

341-
func requiredGatePassed(passed map[string]bool, names ...string) bool {
350+
func testPassed(passed, failed map[string]bool, names ...string) bool {
351+
for _, name := range names {
352+
if failed[name] {
353+
return false
354+
}
355+
}
342356
for _, name := range names {
343357
if passed[name] {
344358
return true
@@ -347,6 +361,47 @@ func requiredGatePassed(passed map[string]bool, names ...string) bool {
347361
return false
348362
}
349363

364+
func inferredAnyRatioGate(passed, failed map[string]bool, names ...string) RatioGate {
365+
for _, name := range names {
366+
if failed[name] {
367+
return RatioGate{Seen: true, Passing: 0, Total: 1}
368+
}
369+
}
370+
return RatioGate{Seen: true, Passing: boolToInt(testPassed(passed, failed, names...)), Total: 1}
371+
}
372+
373+
func inferredAllRatioGate(passed, failed map[string]bool, names ...string) RatioGate {
374+
for _, name := range names {
375+
if failed[name] {
376+
return RatioGate{Seen: true, Passing: 0, Total: 1}
377+
}
378+
}
379+
return RatioGate{Seen: true, Passing: boolToInt(allRequiredTestsPassed(passed, names...)), Total: 1}
380+
}
381+
382+
func allRequiredTestsPassed(passed map[string]bool, names ...string) bool {
383+
for _, name := range names {
384+
if !passed[name] {
385+
return false
386+
}
387+
}
388+
return true
389+
}
390+
391+
func gateResults(gates CutoverGates) []GateResult {
392+
return []GateResult{
393+
{Name: "python_reference_required", Passing: gates.PythonReferenceRequired},
394+
{Name: "go_tests_pass", Passing: gates.GoTests == "pass"},
395+
{Name: "surface_parity", Passing: gates.SurfaceParity == 1.0},
396+
{Name: "help_parity", Passing: gates.HelpParity == 1.0},
397+
{Name: "functional_contracts", Passing: gates.FunctionalContracts == 1.0},
398+
{Name: "state_diff_contracts", Passing: gates.StateDiffContracts == 1.0},
399+
{Name: "python_tests_pass", Passing: gates.PythonTests == "pass"},
400+
{Name: "benchmarks_pass", Passing: gates.Benchmarks == "pass"},
401+
{Name: "no_known_exceptions", Passing: gates.KnownExceptions == 0},
402+
}
403+
}
404+
350405
func passFail(ok bool) string {
351406
if ok {
352407
return "pass"
@@ -361,21 +416,6 @@ func boolToInt(ok bool) int {
361416
return 0
362417
}
363418

364-
func allExplicitGatesSeen(
365-
pythonReference BoolGate,
366-
pythonTests BoolGate,
367-
benchmarks BoolGate,
368-
surface RatioGate,
369-
help RatioGate,
370-
functional RatioGate,
371-
stateDiff RatioGate,
372-
knownExceptionsSeen bool,
373-
) bool {
374-
return pythonReference.Seen && pythonTests.Seen && benchmarks.Seen &&
375-
surface.Seen && help.Seen && functional.Seen && stateDiff.Seen &&
376-
knownExceptionsSeen
377-
}
378-
379419
func knownExceptionsFromEnv(raw string) int {
380420
if raw == "" {
381421
return 0
@@ -388,7 +428,7 @@ func knownExceptionsFromEnv(raw string) int {
388428
}
389429

390430
func approvedExceptionCount(output string) (int, bool) {
391-
if !strings.Contains(output, "approved") || !strings.Contains(output, "exception") {
431+
if !strings.Contains(strings.ToLower(output), "approved") || !strings.Contains(strings.ToLower(output), "exception") {
392432
return 0, false
393433
}
394434
fields := strings.Fields(output)

.github/workflows/migration-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ jobs:
111111

112112
benchmarks:
113113
name: Migration Benchmarks
114-
needs: [parity]
114+
needs: [detect-changes, parity]
115+
if: always() && needs.detect-changes.outputs.should-run == 'true'
115116
runs-on: ubuntu-24.04
116117
steps:
117118
- uses: actions/checkout@v4

apm

507 KB
Binary file not shown.

cmd/apm/cmd_config.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,17 @@ func configPath() string {
2424
func runConfig(args []string) int {
2525
for _, a := range args {
2626
if a == "--help" || a == "-h" {
27-
printCmdHelp("config")
27+
fmt.Println("Usage: apm config [OPTIONS] COMMAND [ARGS]...")
28+
fmt.Println()
29+
fmt.Println(" Configure APM CLI")
30+
fmt.Println()
31+
fmt.Println("Options:")
32+
fmt.Println(" --help Show this message and exit.")
33+
fmt.Println()
34+
fmt.Println("Commands:")
35+
fmt.Println(" get Get a configuration value")
36+
fmt.Println(" set Set a configuration value")
37+
fmt.Println(" unset Unset a configuration value")
2838
return 0
2939
}
3040
}

cmd/apm/cmd_marketplace.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ func printMarketplaceHelp() {
7575
fmt.Println(" validate Validate a marketplace manifest")
7676
fmt.Println()
7777
fmt.Println("Authoring commands:")
78-
fmt.Println(" init Add a 'marketplace:' block to apm.yml")
78+
fmt.Println(" init Add a 'marketplace:' block to apm.yml (scaffolds apm.yml if")
79+
fmt.Println(" missing)")
7980
fmt.Println(" check Validate marketplace entries are resolvable")
8081
fmt.Println(" outdated Show packages with available upgrades")
8182
fmt.Println(" doctor Run environment diagnostics for marketplace publishing")

cmd/apm/cmd_mcp.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88
)
99

1010
var mcpSubcommands = []struct{ name, desc string }{
11-
{"install", "Install an MCP server"},
12-
{"search", "Search MCP servers in registry"},
13-
{"inspect", "Show detailed MCP server information"},
11+
{"install", "Add an MCP server to apm.yml."},
1412
{"list", "List all available MCP servers"},
13+
{"search", "Search MCP servers in registry"},
14+
{"show", "Show detailed MCP server information"},
1515
}
1616

1717
func printMCPHelp() {
@@ -24,7 +24,7 @@ func printMCPHelp() {
2424
fmt.Println()
2525
fmt.Println("Commands:")
2626
for _, sub := range mcpSubcommands {
27-
fmt.Printf(" %-14s%s\n", sub.name, sub.desc)
27+
fmt.Printf(" %-9s%s\n", sub.name, sub.desc)
2828
}
2929
}
3030

@@ -42,7 +42,7 @@ func runMCP(args []string) int {
4242
return runMCPInstall(rest)
4343
case "search":
4444
return runMCPSearch(rest)
45-
case "inspect":
45+
case "inspect", "show":
4646
return runMCPInspect(rest)
4747
case "list":
4848
return runMCPList(rest)
@@ -106,7 +106,7 @@ func runMCPSearch(args []string) int {
106106
func runMCPInspect(args []string) int {
107107
for _, a := range args {
108108
if a == "--help" || a == "-h" {
109-
fmt.Println("Usage: apm mcp inspect [OPTIONS] NAME")
109+
fmt.Println("Usage: apm mcp show [OPTIONS] NAME")
110110
fmt.Println()
111111
fmt.Println(" Show detailed MCP server information")
112112
fmt.Println()

cmd/apm/cmd_plugin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
)
99

1010
var pluginSubcommands = []struct{ name, desc string }{
11-
{"init", "Scaffold a new plugin (plugin.json + apm.yml)"},
11+
{"init", "Scaffold a plugin (creates plugin.json + apm.yml)"},
1212
}
1313

1414
func printPluginHelp() {
@@ -21,7 +21,7 @@ func printPluginHelp() {
2121
fmt.Println()
2222
fmt.Println("Commands:")
2323
for _, sub := range pluginSubcommands {
24-
fmt.Printf(" %-14s%s\n", sub.name, sub.desc)
24+
fmt.Printf(" %-6s%s\n", sub.name, sub.desc)
2525
}
2626
}
2727

cmd/apm/cmd_policy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
)
99

1010
var policySubcommands = []struct{ name, desc string }{
11-
{"status", "Show current policy status and source"},
11+
{"status", "Show the current policy posture (discovery, cache, rules)"},
1212
}
1313

1414
func printPolicyHelp() {
@@ -21,7 +21,7 @@ func printPolicyHelp() {
2121
fmt.Println()
2222
fmt.Println("Commands:")
2323
for _, sub := range policySubcommands {
24-
fmt.Printf(" %-14s%s\n", sub.name, sub.desc)
24+
fmt.Printf(" %-8s%s\n", sub.name, sub.desc)
2525
}
2626
}
2727

cmd/apm/cmd_runtime.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import (
88
)
99

1010
var runtimeSubcommands = []struct{ name, desc string }{
11-
{"setup", "Set up a runtime"},
1211
{"list", "List available and installed runtimes"},
1312
{"remove", "Remove an installed runtime"},
13+
{"setup", "Set up a runtime"},
1414
{"status", "Show active runtime and preference order"},
1515
}
1616

@@ -24,7 +24,7 @@ func printRuntimeHelp() {
2424
fmt.Println()
2525
fmt.Println("Commands:")
2626
for _, sub := range runtimeSubcommands {
27-
fmt.Printf(" %-14s%s\n", sub.name, sub.desc)
27+
fmt.Printf(" %-8s%s\n", sub.name, sub.desc)
2828
}
2929
}
3030

0 commit comments

Comments
 (0)