refactor(brain): C1 L1-watcher — drop broken reverse drift check

Removes the `missingInSettings` reverse check ("plugin documented in
Tooling but disabled in settings.json"). It was broken by design:
Tooling Прил. Н lists tools by human/group name ("Frontend Design
plugin", "Trail of Bits Skills") while settings.json keys are machine
IDs (`name@marketplace`) — the two namespaces never compare. The
`/#\d+\s+([\w-]+(?:@[\w-]+)?)/` scan also captured the first plain word
after "#NN" ("#1 PostgreSQL MCP" → "PostgreSQL"), so every run emitted
~190 lines of WARN noise.

ADR-011 §6.1 specifies only the settings→Tooling direction (the L1
pattern "plugin enabled without Tooling formalization"). That is the
FAIL path and is unchanged. detectDrift now returns `{ missingInTooling }`
only. CLI output is a clean single line on success.

Closes the cosmetic issue flagged in bffdaa9.

TDD: reverse-check test replaced with `not.toHaveProperty
('missingInSettings')`; 12/12 GREEN. Smoke: node tools/l1-watcher.mjs
-> exit 0, "OK — 0 drift" (no WARN block).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-19 07:36:21 +03:00
parent cb681dbd68
commit 2a2ded7a53
2 changed files with 9 additions and 16 deletions
+6 -11
View File
@@ -20,6 +20,11 @@ export function parseAliases(raw) {
return out;
}
// Detects the L1 pattern (ADR-011 §6.1): a plugin enabled in settings.json
// that has no formalization in Tooling Прил. Н. Only the settings→Tooling
// direction is checked — the reverse ("documented but disabled") cannot be
// computed reliably because Tooling lists tools by human/group name while
// settings.json keys are machine IDs (`name@marketplace`).
export function detectDrift(settings, toolingText, aliases = {}) {
const enabled = Object.entries(settings.enabledPlugins || {})
.filter(([, v]) => v === true)
@@ -32,13 +37,7 @@ export function detectDrift(settings, toolingText, aliases = {}) {
if (alias && toolingText.includes(alias)) return false;
return true;
});
const inToolingButNotSettings = [];
const toolingNumbered = toolingText.match(/#\d+\s+([\w-]+(?:@[\w-]+)?)/g) || [];
const toolingPluginNames = toolingNumbered.map((s) => s.split(/\s+/)[1]);
for (const p of toolingPluginNames) {
if (!enabled.includes(p)) inToolingButNotSettings.push(p);
}
return { missingInTooling, missingInSettings: inToolingButNotSettings };
return { missingInTooling };
}
function loadFileMaybe(path) {
@@ -71,10 +70,6 @@ if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/l1-watcher
console.error(`If the plugin is referenced in Tooling under a group/human name, add an alias to tools/.l1-watcher-aliases.txt.`);
process.exit(1);
}
if (drift.missingInSettings.length > 0) {
console.warn(`[l1-watcher] WARN — plugins in Tooling but disabled in settings:`);
drift.missingInSettings.forEach((p) => console.warn(` - ${p}`));
}
console.log(`[l1-watcher] OK — 0 drift`);
process.exit(0);
}
+3 -5
View File
@@ -7,22 +7,20 @@ describe('detectDrift', () => {
const tooling = 'Описание #56 foo@org интегрирован.';
const drift = detectDrift(settings, tooling);
expect(drift.missingInTooling).toEqual(['bar@org']);
expect(drift.missingInSettings).toEqual([]);
});
it('finds plugins in tooling but not in settings', () => {
it('reports only the settings→Tooling direction (no reverse check)', () => {
const settings = { enabledPlugins: { 'foo@org': true } };
const tooling = '#56 foo@org. #57 baz@org включён.';
const drift = detectDrift(settings, tooling);
expect(drift.missingInSettings).toEqual(['baz@org']);
expect(drift).not.toHaveProperty('missingInSettings');
});
it('returns empty arrays when in sync', () => {
it('returns empty missingInTooling when in sync', () => {
const settings = { enabledPlugins: { 'foo@org': true } };
const tooling = '#56 foo@org описан.';
const drift = detectDrift(settings, tooling);
expect(drift.missingInTooling).toEqual([]);
expect(drift.missingInSettings).toEqual([]);
});
it('handles disabled plugins (value false)', () => {