refactor(extensions): simplify lib helpers, tool registration and kit-doctor

Behavior-preserving cleanups:
- extract shared resolveBaseUrl helper for jira/confluence base-URL logic
- extract shared registerTool helper; port jira/gitlab/confluence tools to it
- make scanSecrets declarative (filter/map)
- collapse ok/warn pair into status() and drop double existsSync in kit-doctor

Typecheck clean; npm test 71 PASS, 0 FAIL.
This commit is contained in:
Aleksey Shakhmatov
2026-08-07 00:24:04 +03:00
parent 88eb02e17f
commit 0c6e590273
9 changed files with 212 additions and 202 deletions
+11 -11
View File
@@ -63,40 +63,40 @@ export default function kitCliExtension(pi: ExtensionAPI) {
pi.registerCommand("kit-doctor", {
description: "Проверка окружения pi-kit (node, версия, конфиг, env, токены)",
handler: async (_args, ctx) => {
const ok = (s: string) => `✓ ${s}`;
const warn = (s: string) => `⚠ ${s}`;
const status = (ok: boolean, text: string) => `${ok ? "✓" : "⚠"} ${text}`;
const cfg = readConfig();
const lines: string[] = [];
lines.push(ok(`Node.js ${process.version}`));
lines.push(ok(`pi-kit v${readVersion()}${process.env.PI_KIT_CHANNEL ? ` (канал: ${process.env.PI_KIT_CHANNEL})` : ""}`));
lines.push(status(true, `Node.js ${process.version}`));
lines.push(status(true, `pi-kit v${readVersion()}${process.env.PI_KIT_CHANNEL ? ` (канал: ${process.env.PI_KIT_CHANNEL})` : ""}`));
// Config
const cfgOk = typeof cfg.gitHost === "string" && cfg.gitHost !== "TODO";
lines.push((cfgOk ? ok : warn)(`config: gitHost=${cfg.gitHost ?? "?"}, tracker=${cfg.trackerUrl ?? "?"}, docs=${cfg.docsUrl ?? "?"}`));
if (cfg.remoteConfigUrl) lines.push(ok(`remoteConfigUrl задан`));
lines.push(status(cfgOk, `config: gitHost=${cfg.gitHost ?? "?"}, tracker=${cfg.trackerUrl ?? "?"}, docs=${cfg.docsUrl ?? "?"}`));
if (cfg.remoteConfigUrl) lines.push(status(true, `remoteConfigUrl задан`));
// TODO-placeholder values are a readiness signal, not an error: warn so
// maintainers notice they haven't filled the single source of truth yet.
for (const [field, value] of Object.entries({ repoMap: cfg.repoMap, trackerUrl: cfg.trackerUrl, docsUrl: cfg.docsUrl })) {
if (typeof value !== "string" || value === "TODO" || value.trim() === "" || value.startsWith("TODO")) {
lines.push(warn(`config: ${field} не заполнен (TODO) — скиллы repo-map/docs-map/jira будут просить уточнить`));
lines.push(status(false, `config: ${field} не заполнен (TODO) — скиллы repo-map/docs-map/jira будут просить уточнить`));
}
}
// Env vars (presence only, never values)
for (const v of ["TRACKER_URL", "JIRA_TOKEN", "CONFLUENCE_TOKEN", "GITLAB_TOKEN"]) {
lines.push((process.env[v] ? ok : warn)(`${v} ${process.env[v] ? "задан" : "не задан"}`));
lines.push(status(Boolean(process.env[v]), `${v} ${process.env[v] ? "задан" : "не задан"}`));
}
const provider = PROVIDER_KEYS.find((k) => process.env[k]);
lines.push((provider ? ok : warn)(provider ? `LLM-ключ: ${provider}` : "LLM-ключ провайдера не найден"));
lines.push(status(Boolean(provider), provider ? `LLM-ключ: ${provider}` : "LLM-ключ провайдера не найден"));
// env.sh
lines.push((fs.existsSync(ENV_FILE) ? ok : warn)(`env.sh ${fs.existsSync(ENV_FILE) ? "есть" : "нет"} (${ENV_FILE})`));
const envShExists = fs.existsSync(ENV_FILE);
lines.push(status(envShExists, `env.sh ${envShExists ? "есть" : "нет"} (${ENV_FILE})`));
// Loaded commands/skills
const cmds = pi.getCommands();
const skills = cmds.filter((c) => c.source === "skill").length;
lines.push(ok(`команд загружено: ${cmds.length} (скилов: ${skills})`));
lines.push(status(true, `команд загружено: ${cmds.length} (скилов: ${skills})`));
const warns = lines.filter((l) => l.startsWith("⚠")).length;
lines.push("", warns === 0 ? "Итог: всё ок." : `Итог: предупреждений — ${warns}.`);