refactor(#1307): gate intel on isCapabilityActive + loop-resolver honors capability active (#1315)

* refactor(#1307): gate intel on isCapabilityActive + loop-resolver honors capability active

Part A: intel's command gate moves from config-only isIntelEnabled to the
shared isCapabilityActive('intel', cwd) — a consistency cutover (intel has
skills:[] so its tri-state collapses to the intel.enabled config leg; the gate
now flows through the resolver's precedence + runtime-aware resolution).
Part B: loop-resolver hook rendering now gates on capability state.active
(=== true, fail-closed) instead of state.enabled, so the capability config
gate is honored by the hook consumer, not just per-hook 'when'. active is now
required in the loop-resolver input types. Regression test proves a config-
disabled (active=false) capability's unconditional hook is not rendered.
Part of #1302.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#1307): add changeset for intel + loop-resolver active gate

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-16 00:12:54 -04:00
committed by GitHub
parent e50ead7ad2
commit 14e0709eed
10 changed files with 431 additions and 65 deletions

View File

@@ -315,11 +315,15 @@ describe('verify:post — per-key BVA: each false excludes only that single step
// ─── 5. Surface-disable via capabilityStatesById (pure resolver) ──────────────
describe('verify:post — surface-disable: capabilityStatesById filters hooks', () => {
test('[negative] ui disabled via capabilityStatesById→enabled:false excludes ui step; nyquist+security remain', () => {
// Phase 4 note: the resolver now gates on `active` (not `enabled`), so
// capabilityStatesById entries must carry active:false to suppress a hook.
// Real CapabilityStateEntry objects from resolveCapabilityRuntimeState carry both
// enabled and active; fixtures here mirror that shape.
test('[negative] ui disabled via capabilityStatesById→active:false excludes ui step; nyquist+security remain', () => {
const capabilityStatesById = new Map([
['nyquist', { enabled: true }],
['security', { enabled: true }],
['ui', { enabled: false }],
['nyquist', { enabled: true, active: true }],
['security', { enabled: true, active: true }],
['ui', { enabled: false, active: false }],
]);
const resolved = resolveLoopHooks({
point: 'verify:post',
@@ -340,9 +344,9 @@ describe('verify:post — surface-disable: capabilityStatesById filters hooks',
test('[negative] security disabled via capabilityStatesById excludes security step; nyquist+ui remain', () => {
const capabilityStatesById = new Map([
['nyquist', { enabled: true }],
['security', { enabled: false }],
['ui', { enabled: true }],
['nyquist', { enabled: true, active: true }],
['security', { enabled: false, active: false }],
['ui', { enabled: true, active: true }],
]);
const resolved = resolveLoopHooks({
point: 'verify:post',
@@ -363,9 +367,9 @@ describe('verify:post — surface-disable: capabilityStatesById filters hooks',
test('[empty-resolution] all three disabled via capabilityStatesById returns empty activeHooks with valid envelope', () => {
const capabilityStatesById = new Map([
['nyquist', { enabled: false }],
['security', { enabled: false }],
['ui', { enabled: false }],
['nyquist', { enabled: false, active: false }],
['security', { enabled: false, active: false }],
['ui', { enabled: false, active: false }],
]);
const resolved = resolveLoopHooks({
point: 'verify:post',