Skip to content

feat: plugin-aware modernize rules for wfctl modernize and MCP#304

Merged
intel352 merged 5 commits intomainfrom
copilot/add-plugin-modernization-support
Mar 12, 2026
Merged

feat: plugin-aware modernize rules for wfctl modernize and MCP#304
intel352 merged 5 commits intomainfrom
copilot/add-plugin-modernization-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 12, 2026

wfctl modernize only knew about core engine rules. External plugins had no way to declare migration rules for their own type renames and config key changes, leaving plugin users without automated migration tooling.

New: modernize.ManifestRule — JSON-serializable rule descriptors

Four rule kinds, declared in plugin.json under modernizeRules:

{
  "modernizeRules": [
    { "id": "my-rename-type",   "description": "...", "oldModuleType": "my.v1", "newModuleType": "my.v2" },
    { "id": "my-rename-step",   "description": "...", "oldStepType": "step.old", "newStepType": "step.new" },
    { "id": "my-rename-modkey", "description": "...", "moduleType": "my.v2", "oldKey": "apiEndpoint", "newKey": "endpoint" },
    { "id": "my-rename-stepkey","description": "...", "stepType": "step.new", "oldKey": "timeout_ms", "newKey": "timeoutMs" }
  ]
}

modernize.LoadRulesFromDir(pluginDir) scans */plugin.json files and compiles them into []modernize.Rule with full Check/Fix AST functions.

wfctl modernize --plugin-dir (mirrors validate --plugin-dir)

wfctl modernize --plugin-dir data/plugins config.yaml       # detect
wfctl modernize --apply --plugin-dir data/plugins config.yaml  # fix
wfctl modernize --plugin-dir data/plugins --list-rules      # all rules

In-process Go plugin interface

Optional plugin.ModernizeRulesProvider interface for function-based rules that go beyond pattern matching:

func (p *Plugin) ModernizeRules() []modernize.Rule { ... }

plugin.PluginManifest gains a ModernizeRules []modernize.ManifestRule field so the Go struct mirrors the JSON format.

MCP server

handleModernize automatically includes plugin rules when the server was started with --plugin-dir. No client-side changes needed.

What plugin authors and workflow-registry need to do

  • External plugin authors: add modernizeRules to plugin.json on breaking releases; keep historical rules until the old types can be considered fully retired.
  • GoCodeAlone/workflow-registry: extend RegistryCapabilities with a modernizeRules summary; update wfctl publish to include the rules in registry manifests for discovery.

Documented in docs/PLUGIN_DEVELOPMENT_GUIDE.md (external) and docs/PLUGIN_DEVELOPMENT.md (in-process), with a full field reference table and ecosystem guidance.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for external plugins in wfctl modernize command feat: plugin-aware modernize rules for wfctl modernize and MCP Mar 12, 2026
@intel352 intel352 marked this pull request as ready for review March 12, 2026 11:50
Copilot AI review requested due to automatic review settings March 12, 2026 11:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds plugin-declared migration rules to the modernize ecosystem so wfctl modernize and the MCP modernize tool can detect/fix plugin-specific breaking config changes (type/key renames) when a plugin directory is provided.

Changes:

  • Introduces modernize.ManifestRule (JSON-serializable) plus modernize.LoadRulesFromDir() to compile plugin.json rule descriptors into executable modernize rules.
  • Extends wfctl modernize with --plugin-dir and updates MCP modernize to include external plugin rules when the server started with a plugin directory.
  • Updates plugin manifest structs/docs and adds test coverage for rule parsing/loading and CLI integration.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
plugin/manifest.go Adds ModernizeRules []modernize.ManifestRule to the Go manifest struct to mirror plugin.json.
plugin/engine_plugin.go Introduces optional ModernizeRulesProvider interface for in-process plugins.
modernize/manifest_rule.go Implements ManifestRule, conversion to executable Rule, and LoadRulesFromDir() for external plugins.
modernize/manifest_rule_test.go Adds unit tests for rule validation, rule behavior, and loading rules from plugin.json files.
mcp/wfctl_tools.go Updates MCP modernize tool to append plugin-provided rules when s.pluginDir is set.
docs/WFCTL.md Documents wfctl modernize --plugin-dir and how plugin rules are discovered/used.
docs/PLUGIN_DEVELOPMENT_GUIDE.md Documents modernizeRules in external plugin.json manifests and ecosystem guidance.
docs/PLUGIN_DEVELOPMENT.md Documents in-process plugin modernize rules and the updated manifest struct shape.
cmd/wfctl/modernize.go Adds --plugin-dir flag and loads plugin rules into the modernize rule set.
cmd/wfctl/modernize_test.go Adds CLI tests covering --plugin-dir (empty, missing, list-rules, apply, multiple plugins).
cmd/wfctl/main.go Re-aligns the commands map formatting (no behavior change).

Comment on lines +347 to +355
keyNode := findMapKeyNode(cfg, oldKey)
if keyNode != nil {
changes = append(changes, Change{
RuleID: id,
Line: keyNode.Line,
Description: fmt.Sprintf("Renamed config key %q -> %q in %q step", oldKey, newKey, stepType),
})
keyNode.Value = newKey
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The step config key rename fix has the same key-collision issue as the module variant: renaming oldKey to newKey can create duplicate keys if newKey already exists in the step config mapping. Please handle the collision case explicitly (e.g., remove oldKey instead of renaming, or skip/mark not-fixable) to avoid producing ambiguous YAML.

Copilot uses AI. Check for mistakes.
Comment on lines +1709 to +1713
if s.pluginDir != "" {
pluginRules, err := modernize.LoadRulesFromDir(s.pluginDir)
if err == nil {
rules = append(rules, pluginRules...)
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleModernize silently ignores errors when loading plugin rules (it only appends rules when err==nil). This can make plugin rule loading failures invisible to clients even though the tool description says plugin rules are included when started with a plugin directory. Consider returning a tool error (or at least including a warning field in the tool result) when LoadRulesFromDir fails so users can diagnose misconfigured/unreadable plugin directories.

Copilot uses AI. Check for mistakes.
}
if kinds == 0 {
return fmt.Errorf("modernize rule %q: must specify at least one of: oldModuleType, oldStepType, or moduleType/stepType + oldKey", mr.ID)
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ManifestRule.Validate() says exactly one rule kind must be configured, but it only errors when kinds==0. If a rule descriptor sets multiple kind fields (e.g., oldModuleType plus moduleType/oldKey), Validate() will pass and ToRule() will silently pick the first match, dropping the others. Please enforce kinds==1 (error when kinds>1) so misconfigured manifests fail fast and deterministically.

Suggested change
}
}
if kinds > 1 {
return fmt.Errorf("modernize rule %q: exactly one rule kind must be configured; found %d kinds", mr.ID, kinds)
}

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +126
sev := mr.Severity
if sev == "" {
sev = "warning"
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity is documented/assumed to be "error" or "warning", but ManifestRule.ToRule() passes through any non-empty value without validation. This can lead to inconsistent output or downstream handling if a plugin emits an unexpected severity string. Consider validating mr.Severity in Validate() (allow empty, "warning", "error") and returning a clear error for unsupported values.

Copilot uses AI. Check for mistakes.
Comment on lines +291 to +299
keyNode := findMapKeyNode(cfg, oldKey)
if keyNode != nil {
changes = append(changes, Change{
RuleID: id,
Line: keyNode.Line,
Description: fmt.Sprintf("Renamed config key %q -> %q in %q module", oldKey, newKey, moduleType),
})
keyNode.Value = newKey
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module config key rename fix renames the YAML key node in-place without checking whether newKey already exists in the same mapping. If both oldKey and newKey are present, this will create duplicate keys in the YAML mapping, which can change semantics or make the output confusing. Please detect collisions (newKey already present) and either delete the oldKey entry, merge safely, or mark the finding as not fixable in that scenario.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Mar 12, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:244: parsing iteration count: invalid syntax
baseline-bench.txt:286241: parsing iteration count: invalid syntax
baseline-bench.txt:593109: parsing iteration count: invalid syntax
baseline-bench.txt:1116682: parsing iteration count: invalid syntax
baseline-bench.txt:1425593: parsing iteration count: invalid syntax
baseline-bench.txt:1735131: parsing iteration count: invalid syntax
benchmark-results.txt:244: parsing iteration count: invalid syntax
benchmark-results.txt:317762: parsing iteration count: invalid syntax
benchmark-results.txt:647613: parsing iteration count: invalid syntax
benchmark-results.txt:951487: parsing iteration count: invalid syntax
benchmark-results.txt:1246223: parsing iteration count: invalid syntax
benchmark-results.txt:1555908: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │       benchmark-results.txt        │
                            │       sec/op       │    sec/op     vs base              │
InterpreterCreation-4               7.100m ± 58%   6.275m ± 55%       ~ (p=0.937 n=6)
ComponentLoad-4                     3.429m ±  0%   3.457m ±  1%  +0.80% (p=0.002 n=6)
ComponentExecute-4                  2.013µ ±  2%   1.990µ ±  2%       ~ (p=0.093 n=6)
PoolContention/workers-1-4          1.087µ ±  3%   1.081µ ±  3%       ~ (p=0.461 n=6)
PoolContention/workers-2-4          1.084µ ±  1%   1.080µ ±  2%       ~ (p=0.786 n=6)
PoolContention/workers-4-4          1.094µ ±  1%   1.081µ ±  1%  -1.14% (p=0.006 n=6)
PoolContention/workers-8-4          1.092µ ±  2%   1.083µ ±  0%       ~ (p=0.056 n=6)
PoolContention/workers-16-4         1.090µ ±  1%   1.094µ ±  2%       ~ (p=0.909 n=6)
ComponentLifecycle-4                3.438m ±  0%   3.515m ±  0%  +2.22% (p=0.002 n=6)
SourceValidation-4                  2.274µ ±  0%   2.402µ ±  1%  +5.63% (p=0.002 n=6)
RegistryConcurrent-4                765.8n ±  4%   786.5n ±  3%       ~ (p=0.310 n=6)
LoaderLoadFromString-4              3.451m ±  0%   3.562m ±  1%  +3.21% (p=0.002 n=6)
geomean                             18.45µ         18.42µ        -0.15%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               1.944Mi ± 0%   1.944Mi ± 0%       ~ (p=0.942 n=6)
ComponentLoad-4                     2.097Mi ± 0%   2.097Mi ± 0%       ~ (p=0.461 n=6)
ComponentExecute-4                  1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4         1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                2.099Mi ± 0%   2.099Mi ± 0%       ~ (p=0.513 n=6)
SourceValidation-4                  1.984Ki ± 0%   1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                1.133Ki ± 0%   1.133Ki ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4              2.099Mi ± 0%   2.099Mi ± 0%  -0.00% (p=0.035 n=6)
geomean                             15.05Ki        15.05Ki       -0.00%
¹ all samples are equal

                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │     allocs/op      │  allocs/op   vs base                │
InterpreterCreation-4                15.09k ± 0%   15.09k ± 0%       ~ (p=1.000 n=6) ¹
ComponentLoad-4                      17.43k ± 0%   17.43k ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                    25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4           25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                 17.48k ± 0%   17.48k ± 0%       ~ (p=1.000 n=6) ¹
SourceValidation-4                    32.00 ± 0%    32.00 ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                  2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4               17.47k ± 0%   17.47k ± 0%       ~ (p=1.000 n=6) ¹
geomean                               181.2         181.2       +0.00%
¹ all samples are equal

pkg: github.com/GoCodeAlone/workflow/middleware
                                  │ baseline-bench.txt │       benchmark-results.txt       │
                                  │       sec/op       │   sec/op     vs base              │
CircuitBreakerDetection-4                  285.1n ± 3%   284.9n ± 2%       ~ (p=0.851 n=6)
CircuitBreakerExecution_Success-4          22.48n ± 1%   22.50n ± 0%       ~ (p=0.420 n=6)
CircuitBreakerExecution_Failure-4          64.22n ± 0%   64.17n ± 2%       ~ (p=0.669 n=6)
geomean                                    74.39n        74.38n       -0.01%

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │        B/op        │    B/op     vs base                │
CircuitBreakerDetection-4                 144.0 ± 0%     144.0 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │     allocs/op      │ allocs/op   vs base                │
CircuitBreakerDetection-4                 1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │       sec/op       │    sec/op     vs base              │
JQTransform_Simple-4                     885.9n ± 27%   880.8n ± 27%       ~ (p=0.818 n=6)
JQTransform_ObjectConstruction-4         1.474µ ±  1%   1.452µ ±  0%  -1.49% (p=0.002 n=6)
JQTransform_ArraySelect-4                3.304µ ±  1%   3.314µ ±  1%       ~ (p=0.258 n=6)
JQTransform_Complex-4                    37.92µ ±  3%   38.62µ ±  1%       ~ (p=0.065 n=6)
JQTransform_Throughput-4                 1.794µ ±  1%   1.787µ ±  0%       ~ (p=0.121 n=6)
SSEPublishDelivery-4                     72.08n ±  1%   67.51n ±  0%  -6.35% (p=0.002 n=6)
geomean                                  1.663µ         1.644µ        -1.14%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
JQTransform_Simple-4                   1.273Ki ± 0%     1.273Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4       1.773Ki ± 0%     1.773Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4              2.625Ki ± 0%     2.625Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                  16.22Ki ± 0%     16.22Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4               1.984Ki ± 0%     1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │     allocs/op      │ allocs/op   vs base                │
JQTransform_Simple-4                     10.00 ± 0%     10.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4         15.00 ± 0%     15.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4                30.00 ± 0%     30.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                    324.0 ± 0%     324.0 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4                 17.00 ± 0%     17.00 ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
                                    │ baseline-bench.txt │       benchmark-results.txt       │
                                    │       sec/op       │   sec/op     vs base              │
SchemaValidation_Simple-4                   1.108µ ± 12%   1.123µ ± 3%       ~ (p=0.623 n=6)
SchemaValidation_AllFields-4                1.681µ ±  3%   1.735µ ± 4%       ~ (p=0.143 n=6)
SchemaValidation_FormatValidation-4         1.608µ ±  1%   1.593µ ± 5%       ~ (p=0.223 n=6)
SchemaValidation_ManySchemas-4              1.831µ ±  2%   1.830µ ± 2%       ~ (p=0.937 n=6)
geomean                                     1.530µ         1.544µ       +0.88%

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │        B/op        │    B/op     vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │     allocs/op      │ allocs/op   vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │       sec/op       │    sec/op     vs base               │
EventStoreAppend_InMemory-4                1.214µ ± 36%   1.190µ ± 12%        ~ (p=0.699 n=6)
EventStoreAppend_SQLite-4                  1.828m ±  3%   1.401m ±  4%  -23.34% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           14.01µ ±  2%   14.06µ ±  3%        ~ (p=0.818 n=6)
GetTimeline_InMemory/events-50-4           78.01µ ±  3%   77.93µ ± 20%        ~ (p=1.000 n=6)
GetTimeline_InMemory/events-100-4          157.5µ ± 19%   127.5µ ±  1%  -19.02% (p=0.015 n=6)
GetTimeline_InMemory/events-500-4          658.8µ ±  0%   658.4µ ±  1%        ~ (p=0.699 n=6)
GetTimeline_InMemory/events-1000-4         1.354m ±  1%   1.336m ±  1%   -1.32% (p=0.009 n=6)
GetTimeline_SQLite/events-10-4             109.2µ ±  1%   111.2µ ±  2%   +1.83% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             253.1µ ±  1%   256.1µ ±  1%   +1.18% (p=0.004 n=6)
GetTimeline_SQLite/events-100-4            427.6µ ±  0%   432.3µ ±  1%   +1.09% (p=0.015 n=6)
GetTimeline_SQLite/events-500-4            1.833m ±  0%   1.822m ±  1%        ~ (p=0.240 n=6)
GetTimeline_SQLite/events-1000-4           3.561m ±  0%   3.552m ±  1%        ~ (p=0.132 n=6)
geomean                                    233.5µ         224.4µ         -3.90%

                                   │ baseline-bench.txt │         benchmark-results.txt         │
                                   │        B/op        │     B/op       vs base                │
EventStoreAppend_InMemory-4                  784.5 ± 7%     781.0 ± 11%       ~ (p=0.777 n=6)
EventStoreAppend_SQLite-4                  1.983Ki ± 2%   1.985Ki ±  2%       ~ (p=0.736 n=6)
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%   7.953Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%   46.62Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%   94.48Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%   472.8Ki ±  0%       ~ (p=1.000 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ±  0%       ~ (p=0.141 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ±  0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ±  0%       ~ (p=1.000 n=6)
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ±  0%       ~ (p=0.054 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ±  0%       ~ (p=0.675 n=6)
geomean                                    67.30Ki        67.28Ki        -0.03%
¹ all samples are equal

                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │     allocs/op      │  allocs/op   vs base                │
EventStoreAppend_InMemory-4                  7.000 ± 0%    7.000 ± 0%       ~ (p=1.000 n=6) ¹
EventStoreAppend_SQLite-4                    53.00 ± 0%    53.00 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-10-4             125.0 ± 0%    125.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4             653.0 ± 0%    653.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4           1.306k ± 0%   1.306k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4           6.514k ± 0%   6.514k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-1000-4          13.02k ± 0%   13.02k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-10-4               382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.852k ± 0%   1.852k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             3.681k ± 0%   3.681k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             18.54k ± 0%   18.54k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            37.29k ± 0%   37.29k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.162k        1.162k       +0.00%
¹ all samples are equal

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

@intel352
Copy link
Contributor

@copilot apply changes based on the comments in this thread

…tection, MCP error handling, and interface comments

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot apply changes based on the comments in this thread

Applied all review feedback in 7948c76:

  • Validate() kinds > 1: Now returns an error when multiple rule kinds are set simultaneously — deterministic fail-fast behavior.
  • Severity validation: Validate() now rejects any non-empty severity that isn't "error" or "warning".
  • Key collision detection (module + step): Both toModuleConfigKeyRenameRule and toStepConfigKeyRenameRule now check whether newKey already exists in the config mapping. When it does, the finding is reported as Fixable: false with a "cannot auto-rename: X already exists" message, and the Fix function skips the rename to avoid producing duplicate YAML keys.
  • MCP plugin load errors: handleModernize now returns an early tool result with a plugin_warning field when LoadRulesFromDir fails, making the failure visible to clients instead of silently continuing with only built-in rules.
  • ModernizeRulesProvider comment: Updated to clearly state this is an API/extension point, not yet wired into the engine.
  • docs/PLUGIN_DEVELOPMENT.md: Corrected the claim about the engine calling ModernizeRules() — now accurately says "Support for consuming ModernizeRules() from in-process plugins is not yet wired into the engine; this interface is reserved for future use."

@intel352
Copy link
Contributor

@copilot Make sure linter is passing

…ocritic linter

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot Make sure linter is passing

Fixed in 4da198b — the one linter issue was a gocritic ifElseChain in module/pipeline_step_graphql.go:320 (rewritten as a switch statement). golangci-lint run ./... now reports 0 issues.

@intel352 intel352 merged commit 93d24ba into main Mar 12, 2026
11 checks passed
@intel352 intel352 deleted the copilot/add-plugin-modernization-support branch March 12, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants