Files
portal/tools/l1-watcher.mjs
T
Дмитрий 2a2ded7a53 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>
2026-05-19 07:36:21 +03:00

76 lines
3.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
const PLUGIN_NAME_PATTERN = /[a-z][\w-]*(?:@[\w-]+)?/g;
export function parseAliases(raw) {
if (!raw) return {};
const out = {};
for (const line of raw.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 1) continue;
const key = trimmed.slice(0, eq).trim();
const value = trimmed.slice(eq + 1).trim();
if (key && value) out[key] = value;
}
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)
.map(([k]) => k);
const found = new Set();
for (const m of toolingText.matchAll(PLUGIN_NAME_PATTERN)) found.add(m[0]);
const missingInTooling = enabled.filter((p) => {
if (found.has(p)) return false;
const alias = aliases[p];
if (alias && toolingText.includes(alias)) return false;
return true;
});
return { missingInTooling };
}
function loadFileMaybe(path) {
try {
return existsSync(path) ? readFileSync(path, 'utf-8') : null;
} catch {
return null;
}
}
export function loadInputs(projectRoot = process.cwd()) {
const userSettings = JSON.parse(loadFileMaybe(join(homedir(), '.claude', 'settings.json')) || '{}');
const projectSettings = JSON.parse(loadFileMaybe(join(projectRoot, '.claude', 'settings.json')) || '{}');
const merged = {
enabledPlugins: { ...(userSettings.enabledPlugins || {}), ...(projectSettings.enabledPlugins || {}) },
};
const tooling = loadFileMaybe(join(projectRoot, 'docs', 'Tooling_v8_3.md')) || '';
const aliasesRaw = loadFileMaybe(join(projectRoot, 'tools', '.l1-watcher-aliases.txt'));
const aliases = parseAliases(aliasesRaw);
return { settings: merged, tooling, aliases };
}
if (process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/l1-watcher.mjs')) {
const { settings, tooling, aliases } = loadInputs();
const drift = detectDrift(settings, tooling, aliases);
if (drift.missingInTooling.length > 0) {
console.error(`[l1-watcher] FAIL — plugins in settings but not formalized in Tooling Прил. Н:`);
drift.missingInTooling.forEach((p) => console.error(` - ${p}`));
console.error(`Run /claude-md-management:claude-md-improver to formalize.`);
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);
}
console.log(`[l1-watcher] OK — 0 drift`);
process.exit(0);
}