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

@@ -963,3 +963,78 @@ describe('--active-cap flag (loop render-hooks)', () => {
);
});
});
// ─── Phase 4 regression: loop-resolver gates on state.active (not state.enabled) ─────
//
// FAIL-FIRST PROOF (what would fail against the OLD state.enabled check):
// Scenario: capability has activationKey set; it is installed + surfaced (enabled=true)
// but the activationKey resolves to false (active=false). The capability has a hook
// WITHOUT a `when` guard (unconditional). With old state.enabled check:
// state.enabled=true → state.enabled !== false → true → hook IS rendered (BUG).
// With new state.active check:
// state.active=false → state.active === true → false → hook NOT rendered (CORRECT).
//
// This test uses resolveLoopHooks directly with a synthetic registry and a
// capabilityStatesById map that models the above scenario: enabled=true, active=false.
// It would FAIL against the OLD `state.enabled !== false` code and PASS against the
// NEW `state.active === true` code.
describe('Phase 4 regression: capabilityStatesById gates on active (not enabled) for config-disabled capability', () => {
test('[regression] enabled=true active=false + no `when` guard → hook NOT rendered (state.active gate)', () => {
// Fail-first: with OLD `state.enabled !== false`, enabled=true → hook IS rendered (BUG).
// With NEW `state.active === true`, active=false → hook NOT rendered (CORRECT).
const registry = makeRegistry({
point: 'plan:pre',
steps: [{ capId: 'test-cap', ref: { skill: 'gsd-test-skill' } }],
// No `when` → unconditional hook (no per-hook config gate to fall back on)
});
const capabilityStatesById = new Map([
// enabled=true (installed+surfaced), active=false (activationKey resolved to false)
// This models a capability like intel/graphify that is surfaced but config-disabled.
['test-cap', { enabled: true, active: false }],
]);
const result = resolveLoopHooks({
point: 'plan:pre',
registry,
config: {}, // config doesn't matter — capability already resolved active=false
capabilityStatesById,
});
assert.strictEqual(
result.activeHooks.length,
0,
'Hook must NOT be rendered when state.active=false, even if enabled=true and no `when` guard — ' +
'OLD state.enabled check would include this hook (BUG: enabled=true passes enabled!==false); ' +
'NEW state.active check correctly suppresses it (active=false fails active===true)',
);
});
test('[positive control] enabled=true active=true + no `when` guard → hook IS rendered', () => {
// Confirms the fixture is sound: same hook, same registry, but active=true → rendered.
// This test must PASS against BOTH old and new code (it's the unbroken branch).
const registry = makeRegistry({
point: 'plan:pre',
steps: [{ capId: 'test-cap', ref: { skill: 'gsd-test-skill' } }],
});
const capabilityStatesById = new Map([
['test-cap', { enabled: true, active: true }],
]);
const result = resolveLoopHooks({
point: 'plan:pre',
registry,
config: {},
capabilityStatesById,
});
assert.strictEqual(
result.activeHooks.length,
1,
'Hook MUST be rendered when state.active=true and no `when` guard (positive control)',
);
assert.strictEqual(result.activeHooks[0].capId, 'test-cap');
});
});