{ "id": "refactor-trigger", "role": "feature", "version": "1.11.0", "title": "Complexity-triggered refactor", "description": "Measures the complexity of the code a phase touched and, when a function crosses a configured threshold or jumps past its recorded anchor, surfaces a scoped refactor proposal at .planning/phases//-REFACTOR.md. Advisory by default — it never edits code and never blocks. Opt-in strict mode blocks /gsd-ship while a proposal is untriaged; a declined proposal is recorded in the broken-windows ledger when that capability is present. Operationalizes 'refactor early, refactor often' as continuous pressure instead of a thing you have to remember (issue #1953).", "tier": "full", "requires": [], "engines": { "gsd": ">=1.10.0" }, "runtimeCompat": { "supported": [ "*" ], "unsupported": [] }, "skills": [], "agents": [], "activationKey": "refactor.trigger_enabled", "config": { "refactor.trigger_enabled": { "type": "boolean", "default": false, "description": "Enable the complexity-triggered refactor hook. When true, an execute:post step evaluates the complexity of the files the phase touched and writes a scoped refactor proposal if a function crosses refactor.complexity_threshold or jumps past refactor.complexity_jump_delta. Opt-in; when false the hook never runs. Issue #1953." }, "refactor.complexity_threshold": { "type": "number", "default": 15, "description": "Absolute per-function complexity above which a refactor proposal is surfaced. Semantics match ESLint's `complexity: {max: N}` — the trigger is STRICTLY GREATER, so a score of exactly N does not trigger. Default 15 follows SonarSource's default; ESLint's own default is 20 and radon's rank C begins at 11. Raise it if proposals feel like noise." }, "refactor.complexity_jump_delta": { "type": "number", "default": 5, "description": "Complexity growth above which a refactor proposal is surfaced even when the absolute threshold is not reached. Measured against the function's anchor — the score recorded the last time the function was consciously dispositioned — so it accumulates across phases and catches slow creep the absolute threshold would miss. Strictly greater, as with the threshold." }, "refactor.trigger_strict": { "type": "boolean", "default": false, "description": "Record an untriaged refactor proposal as an open `deviation` entry in the broken-windows ledger, so it becomes a tracked task that must be resolved before ship. Off by default and deliberately so: a blocking complexity number is a metric an executor can satisfy by splitting one coherent function into two incoherent ones, so the entry clears on the proposal being DISPOSITIONED (gsd-tools refactor accept|decline), never on the score improving. Ship blocking is the broken-windows capability's existing ship:pre gate — enable it with workflow.windows_enforce. With broken-windows absent, strict mode still records the proposal locally and says so; it cannot block on its own. Advisory mode (the default) surfaces the same proposal and tracks nothing." } }, "commands": [ { "family": "refactor", "module": "refactor-trigger-command-router.cjs", "router": "routeRefactorTriggerCommand" } ], "hooks": [], "steps": [ { "point": "execute:post", "ref": { "command": "refactor evaluate" }, "produces": [ "REFACTOR.md" ], "consumes": [], "when": "refactor.trigger_enabled", "onError": "skip" } ], "contributions": [], "gates": [] }