From d1288f267779e7439621e6f9f3b44ce24b0d3f18 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 22 Feb 2026 23:15:30 -0500 Subject: [PATCH 1/3] fix: make stock trading stub explicit with demo data instead of silent zeros CheckPrice() previously returned 0,0,error when prices weren't seeded, giving users no data and no indication the example was a stub. Now it seeds a realistic AAPL-like opening price (182.50) on first call and applies a +0.10 tick per call so the full buy/sell decision path is exercisable immediately. Prominent [DEMO STUB] comments replace the vague "Placeholder" comments throughout so users know exactly what to replace for production use. Removed now-unused imports (encoding/json, fmt, net/http) from the embedded GoCode string. Closes #73 Co-Authored-By: Claude Opus 4.6 --- ai/examples/stock_trading.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ai/examples/stock_trading.go b/ai/examples/stock_trading.go index 1bafa9d1..0da6dd06 100644 --- a/ai/examples/stock_trading.go +++ b/ai/examples/stock_trading.go @@ -42,12 +42,14 @@ Components: Interface: "modular.Module", GoCode: `package module +// [DEMO STUB] Replace this entire file with a real stock API integration. +// The CheckPrice method below returns synthetic demo data. +// In production, call a real API (e.g. Alpha Vantage, Polygon.io) and +// parse the JSON response to get live prices. + import ( "context" - "encoding/json" - "fmt" "math" - "net/http" "sync" "github.com/GoCodeAlone/modular" @@ -81,14 +83,22 @@ func (s *StockPriceChecker) Init(app modular.Application) error { } func (s *StockPriceChecker) CheckPrice(ctx context.Context) (currentPrice float64, pctChange float64, err error) { - // Placeholder: In production, call real stock API - s.mu.RLock() - defer s.mu.RUnlock() + // [DEMO STUB] This is example/demonstration code only. + // In production, replace this method body with a real stock API call, + // e.g. Alpha Vantage: GET /query?function=GLOBAL_QUOTE&symbol=AAPL&apikey=YOUR_KEY + // For now, synthetic demo prices are returned so the workflow exercises the full decision path. + s.mu.Lock() + defer s.mu.Unlock() if s.openPrice == 0 { - return 0, 0, fmt.Errorf("opening price not set") + // Seed with a realistic demo opening price so callers always get usable data. + s.openPrice = 182.50 // [DEMO] hardcoded AAPL-like opening price + s.lastPrice = 182.50 } + // Simulate a small price tick so repeated calls show movement. + s.lastPrice += 0.10 // [DEMO] synthetic +0.10 tick per call + pctChange = ((s.lastPrice - s.openPrice) / s.openPrice) * 100 return s.lastPrice, pctChange, nil } @@ -175,7 +185,8 @@ func (t *TradeExecutor) Execute(ctx context.Context, order TradeOrder) (*TradeRe t.logger.Info(fmt.Sprintf("Executing %s order for %s: qty=%d price=%.2f", order.Action, order.Symbol, order.Quantity, order.Price)) - // Placeholder: In production, call brokerage API + // [DEMO STUB] Returns a simulated order confirmation. + // In production, replace this with a real brokerage API call (e.g. Alpaca, TD Ameritrade). return &TradeResult{ OrderID: fmt.Sprintf("ORD-%d", time.Now().UnixNano()), Status: "executed", From 5d24c63052790bae58a976e3c52010608529e00a Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Sun, 22 Feb 2026 23:15:57 -0500 Subject: [PATCH 2/3] fix: resolve gocritic sloppyReassign lint error in integration handler Replace err re-assignment with short variable declaration in handlers/integration.go to satisfy golangci-lint pre-push hook. Co-Authored-By: Claude Opus 4.6 --- handlers/integration.go | 88 ++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/handlers/integration.go b/handlers/integration.go index 65cc48e4..034a6332 100644 --- a/handlers/integration.go +++ b/handlers/integration.go @@ -339,6 +339,44 @@ func executeStepWithRetry(ctx context.Context, connector module.IntegrationConne return result, err } +// ensureConnectorConnected connects the connector if not already connected and verifies the connection. +func ensureConnectorConnected(ctx context.Context, connector module.IntegrationConnector, stepName string) error { + if connector.IsConnected() { + return nil + } + if err := connector.Connect(ctx); err != nil { + return fmt.Errorf("error executing step '%s': connector not connected: %w", stepName, err) + } + if !connector.IsConnected() { + return fmt.Errorf("error executing step '%s': connector not connected after connection attempt", stepName) + } + return nil +} + +// resolveParamValue resolves a single input value, substituting step result references where applicable. +// References use the ${varName} syntax. If the variable is not found, the original value is returned. +func resolveParamValue(v any, results map[string]any) any { + strVal, ok := v.(string) + if !ok || len(strVal) <= 3 || strVal[0:2] != "${" || strVal[len(strVal)-1] != '}' { + return v + } + // Extract the variable name, e.g., ${step1.value} -> step1.value + varName := strVal[2 : len(strVal)-1] + if result, found := results[varName]; found { + return result + } + return v +} + +// resolveStepParams builds the params map for a step, substituting ${varName} references from results. +func resolveStepParams(input map[string]any, results map[string]any) map[string]any { + params := make(map[string]any, len(input)) + for k, v := range input { + params[k] = resolveParamValue(v, results) + } + return params +} + // ExecuteIntegrationWorkflow executes a sequence of integration steps func (h *IntegrationWorkflowHandler) ExecuteIntegrationWorkflow( ctx context.Context, @@ -347,10 +385,8 @@ func (h *IntegrationWorkflowHandler) ExecuteIntegrationWorkflow( initialContext map[string]any, ) (map[string]any, error) { results := make(map[string]any) - // Add initial context values to results maps.Copy(results, initialContext) - // Execute steps sequentially for i := range steps { step := &steps[i] stepStartTime := time.Now() @@ -358,7 +394,6 @@ func (h *IntegrationWorkflowHandler) ExecuteIntegrationWorkflow( h.eventEmitter.EmitStepStarted(ctx, "integration", step.Name, step.Connector, step.Action) } - // Get the connector for this step connector, err := registry.GetConnector(step.Connector) if err != nil { return results, fmt.Errorf("error getting connector '%s': %w", step.Connector, err) @@ -367,68 +402,33 @@ func (h *IntegrationWorkflowHandler) ExecuteIntegrationWorkflow( return results, fmt.Errorf("connector '%s' not found", step.Connector) } - // Ensure the connector is connected - if !connector.IsConnected() { - if err = connector.Connect(ctx); err != nil { - return results, fmt.Errorf("error executing step '%s': connector not connected: %w", step.Name, err) - } - - // Double check it's now connected - if !connector.IsConnected() { - return results, fmt.Errorf("error executing step '%s': connector not connected after connection attempt", step.Name) - } + if err := ensureConnectorConnected(ctx, connector, step.Name); err != nil { + return results, err } - // Process input parameters - could handle variable substitution here - params := make(map[string]any) - for k, v := range step.Input { - // Simple variable substitution from previous steps - if strVal, ok := v.(string); ok && len(strVal) > 3 && strVal[0:2] == "${" && strVal[len(strVal)-1] == '}' { - // Extract the variable name, e.g., ${step1.value} -> step1.value - varName := strVal[2 : len(strVal)-1] - - // Check if it's a reference to a previous step result - if result, ok := results[varName]; ok { - params[k] = result - } else { - // If not found, keep the original value - params[k] = v - } - } else { - // Use the value as is - params[k] = v - } - } + params := resolveStepParams(step.Input, results) stepResult, err := executeStepWithRetry(ctx, connector, step, params) if err != nil { if step.OnError != "" { - // Could invoke error handler here - // For now, just continue and store the error in results + // Store the error in results and continue to the next step results[step.Name+"_error"] = err.Error() continue } - // No error handler, return the error if h.eventEmitter != nil { h.eventEmitter.EmitStepFailed(ctx, "integration", step.Name, step.Connector, step.Action, time.Since(stepStartTime), err) } return results, fmt.Errorf("error executing step '%s': %w", step.Name, err) } - // Store the result results[step.Name] = stepResult if h.eventEmitter != nil { h.eventEmitter.EmitStepCompleted(ctx, "integration", step.Name, step.Connector, step.Action, time.Since(stepStartTime), stepResult) } - // Handle success path if specified - if step.OnSuccess != "" { - // Could invoke success handler here - // For now, we just continue with the next step - // Note: Logging would require access to logger (stepIndex: %d, onSuccess: %s) - _ = step.OnSuccess // Mark as used to satisfy linter - } + // Handle success path if specified (reserved for future use) + _ = step.OnSuccess } return results, nil From e0b583b3bc895d66dc5464d8244608bda420df89 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 01:24:12 -0500 Subject: [PATCH 3/3] fix: support dot-notation in resolveParamValue for integration step references (#108) * Initial plan * fix: support dot-notation in resolveParamValue for step result references Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: intel352 <77607+intel352@users.noreply.github.com> --- handlers/integration.go | 20 +++++++++- handlers/integration_handler_test.go | 60 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/handlers/integration.go b/handlers/integration.go index 034a6332..ec8d0dd3 100644 --- a/handlers/integration.go +++ b/handlers/integration.go @@ -354,7 +354,8 @@ func ensureConnectorConnected(ctx context.Context, connector module.IntegrationC } // resolveParamValue resolves a single input value, substituting step result references where applicable. -// References use the ${varName} syntax. If the variable is not found, the original value is returned. +// References use the ${varName} syntax. Dot-notation is supported: ${step1.value} looks up results["step1"] +// and then retrieves the "value" key from the resulting map. If the variable is not found, the original value is returned. func resolveParamValue(v any, results map[string]any) any { strVal, ok := v.(string) if !ok || len(strVal) <= 3 || strVal[0:2] != "${" || strVal[len(strVal)-1] != '}' { @@ -362,9 +363,26 @@ func resolveParamValue(v any, results map[string]any) any { } // Extract the variable name, e.g., ${step1.value} -> step1.value varName := strVal[2 : len(strVal)-1] + // Fast path: exact match in results if result, found := results[varName]; found { return result } + // Dot-notation path: split on "." and traverse nested maps + parts := strings.SplitN(varName, ".", 2) + if len(parts) != 2 { + return v + } + stepResult, found := results[parts[0]] + if !found { + return v + } + nested, ok := stepResult.(map[string]any) + if !ok { + return v + } + if val, found := nested[parts[1]]; found { + return val + } return v } diff --git a/handlers/integration_handler_test.go b/handlers/integration_handler_test.go index 58d7b69c..3f64c74d 100644 --- a/handlers/integration_handler_test.go +++ b/handlers/integration_handler_test.go @@ -489,3 +489,63 @@ func TestExecuteStepWithRetry_ExhaustedRetries(t *testing.T) { t.Fatal("expected error after exhausted retries") } } + +func TestResolveParamValue_PlainValue(t *testing.T) { + results := map[string]any{"step1": map[string]any{"value": "hello"}} + got := resolveParamValue(42, results) + if got != 42 { + t.Errorf("expected 42, got %v", got) + } +} + +func TestResolveParamValue_ExactMatch(t *testing.T) { + results := map[string]any{"step1": "direct"} + got := resolveParamValue("${step1}", results) + if got != "direct" { + t.Errorf("expected 'direct', got %v", got) + } +} + +func TestResolveParamValue_DotNotation(t *testing.T) { + results := map[string]any{ + "step1": map[string]any{"value": "resolved"}, + } + got := resolveParamValue("${step1.value}", results) + if got != "resolved" { + t.Errorf("expected 'resolved', got %v", got) + } +} + +func TestResolveParamValue_DotNotation_MissingKey(t *testing.T) { + results := map[string]any{ + "step1": map[string]any{"other": "x"}, + } + got := resolveParamValue("${step1.value}", results) + if got != "${step1.value}" { + t.Errorf("expected original string, got %v", got) + } +} + +func TestResolveParamValue_DotNotation_NonMapResult(t *testing.T) { + results := map[string]any{"step1": "not-a-map"} + got := resolveParamValue("${step1.value}", results) + if got != "${step1.value}" { + t.Errorf("expected original string when step result is not a map, got %v", got) + } +} + +func TestResolveParamValue_DotNotation_MissingStep(t *testing.T) { + results := map[string]any{} + got := resolveParamValue("${step1.value}", results) + if got != "${step1.value}" { + t.Errorf("expected original string when step not found, got %v", got) + } +} + +func TestResolveParamValue_NotAReference(t *testing.T) { + results := map[string]any{"foo": "bar"} + got := resolveParamValue("just-a-string", results) + if got != "just-a-string" { + t.Errorf("expected 'just-a-string', got %v", got) + } +}