Files
portal/tools/l1-watcher.mjs
T

76 lines
3.0 KiB
JavaScript
Raw Normal View History

#!/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);
}