@@ -20,6 +20,7 @@ import (
2020 "github.com/step-security/dev-machine-guard/internal/detector/configaudit"
2121 "github.com/step-security/dev-machine-guard/internal/device"
2222 "github.com/step-security/dev-machine-guard/internal/executor"
23+ "github.com/step-security/dev-machine-guard/internal/featuregate"
2324 "github.com/step-security/dev-machine-guard/internal/launchd"
2425 "github.com/step-security/dev-machine-guard/internal/output"
2526 "github.com/step-security/dev-machine-guard/internal/paths"
@@ -47,6 +48,12 @@ func main() {
4748 // minimal config.Load (just enough for the upload gate) so this branch
4849 // stays free of the rest of main's setup work.
4950 if len (os .Args ) >= 2 && os .Args [1 ] == "_hook" {
51+ // Gated: silently no-op so any pre-existing hook entry that points
52+ // at this binary stays harmless until the feature ships. Override
53+ // flows in via STEPSECURITY_OVERRIDE_GATE since Parse hasn't run.
54+ if ! featuregate .IsEnabled (featuregate .FeatureAIAgentHooks ) {
55+ os .Exit (0 )
56+ }
5057 os .Exit (aiagentscli .RunHook (os .Stdin , os .Stdout , os .Stderr , os .Args [2 :]))
5158 }
5259
@@ -59,6 +66,10 @@ func main() {
5966 os .Exit (1 )
6067 }
6168
69+ if cfg .OverrideGate {
70+ featuregate .EnableOverride ()
71+ }
72+
6273 // Apply saved config values if CLI didn't explicitly override them.
6374 // CLI flags always win over config file values (same as the shell script).
6475 if len (config .SearchDirs ) > 0 && len (cfg .SearchDirs ) == 1 && cfg .SearchDirs [0 ] == "$HOME" {
@@ -298,22 +309,38 @@ func main() {
298309 }
299310
300311 case "hooks install" :
312+ if ! featuregate .IsEnabled (featuregate .FeatureAIAgentHooks ) {
313+ fmt .Fprintln (os .Stderr , featuregate .UnavailableMessage ("hooks install" ))
314+ os .Exit (1 )
315+ }
301316 os .Exit (aiagentscli .RunInstall (context .Background (), exec , cfg .HooksAgent , os .Stdout , os .Stderr ))
302317
303318 case "hooks uninstall" :
319+ if ! featuregate .IsEnabled (featuregate .FeatureAIAgentHooks ) {
320+ fmt .Fprintln (os .Stderr , featuregate .UnavailableMessage ("hooks uninstall" ))
321+ os .Exit (1 )
322+ }
304323 os .Exit (aiagentscli .RunUninstall (context .Background (), exec , cfg .HooksAgent , os .Stdout , os .Stderr ))
305324
306325 default :
307326 // --npmrc and --pipconfig: focused, verbose pretty audits that
308327 // bypass everything else for a fast (~1s) deep dive.
309328 if cfg .NPMRCOnly {
329+ if ! featuregate .IsEnabled (featuregate .FeatureNPMRCAudit ) {
330+ fmt .Fprintln (os .Stderr , featuregate .UnavailableMessage ("--npmrc" ))
331+ os .Exit (1 )
332+ }
310333 if err := runNPMRCOnly (exec , cfg ); err != nil {
311334 log .Error ("%v" , err )
312335 os .Exit (1 )
313336 }
314337 return
315338 }
316339 if cfg .PipConfigOnly {
340+ if ! featuregate .IsEnabled (featuregate .FeaturePipConfigAudit ) {
341+ fmt .Fprintln (os .Stderr , featuregate .UnavailableMessage ("--pipconfig" ))
342+ os .Exit (1 )
343+ }
317344 if err := runPipConfigOnly (exec , cfg ); err != nil {
318345 log .Error ("%v" , err )
319346 os .Exit (1 )
@@ -432,6 +459,10 @@ func findLegacyLeftovers(legacy string) []string {
432459// in community mode (enterprise config missing) — the existing scan
433460// path stays unaffected. Failures are logged but never crash main.
434461func runHookStateReconcile (exec executor.Executor , log * progress.Logger ) {
462+ if ! featuregate .IsEnabled (featuregate .FeatureAIAgentHooks ) {
463+ log .Debug ("hook-state reconcile: skipped (feature gated)" )
464+ return
465+ }
435466 cfg , ok := ingest .Snapshot ()
436467 if ! ok {
437468 log .Debug ("hook-state reconcile: skipped (no enterprise config)" )
0 commit comments