Phase 7: kb-objects-audit + первый weekly report (score 84)
Новый скрипт scripts/kb-objects-audit.py — еженедельный health-check vault'а: 1. Каждый projects/<dir>/README.md имеет валидный frontmatter (type/status/aliases) 2. Каждый онлайн-netbird-пир привязан к проекту через aliases или собственную карточку 3. Битые wiki-ссылки [[...]] не указывают в небытие Output: audit/YYYY-MM-DD-objects-audit.md со score (меньше = лучше). Первый запуск 2026-05-06: score=84 - 12/12 проектов с frontmatter ✓ - 3 online orphan-пира (DESKTOP-2IOQS54 Saransk, DESKTOP-AGBMLPN Helsinki, DESKTOP-HL0BB05 Lipetsk) - 26 битых wiki-ссылок выявлено Phase 6: dreaming включён (cron 0 3 * * *), recall promote'нул 17/39, weekly cron на promote. Phase 8: на 137 — minScore=0.4 в memorySearch.query, IDENTITY.md разводит двух Максимок, INFRASTRUCTURE.md переписан как навигатор по vault'у (не дубль). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
62
audit/2026-05-06-objects-audit.md
Normal file
62
audit/2026-05-06-objects-audit.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
date: 2026-05-06
|
||||
type: audit
|
||||
source: scripts/kb-objects-audit.py
|
||||
tags: [audit, objects, frontmatter, links]
|
||||
score: 84
|
||||
---
|
||||
|
||||
# KB objects audit — 2026-05-06
|
||||
|
||||
**Score (меньше = лучше): `84`**
|
||||
|
||||
- Проектов с frontmatter: **12/12** (0 проблем)
|
||||
- NetBird online-пиров без проектной карточки: **3**
|
||||
- Битых wiki-ссылок `[[...]]`: **26**
|
||||
|
||||
## Frontmatter в projects/
|
||||
|
||||
✅ все проекты имеют валидный frontmatter
|
||||
|
||||
## Online netbird-пиры без проектной карточки
|
||||
|
||||
Эти пиры онлайн в NetBird, но не привязаны ни к одной projects/-странице.
|
||||
Бот не сможет ответить «найди X» осмысленно — нет файла или alias.
|
||||
|
||||
Лечение: либо создать stub в `projects/<slug>/README.md` (см. `projects/lipki/` как образец),
|
||||
либо добавить имя пира как полную строку в `aliases` подходящего проекта.
|
||||
|
||||
| NetBird-имя | IP | OS | Город |
|
||||
|---|---|---|---|
|
||||
| `DESKTOP-2IOQS54` | 100.70.82.83 | Windows 10 | Saransk |
|
||||
| `DESKTOP-AGBMLPN` | 100.70.0.106 | Windows 11 | Helsinki |
|
||||
| `DESKTOP-HL0BB05` | 100.70.235.80 | Windows 11 | Lipetsk |
|
||||
|
||||
## Битые wiki-ссылки
|
||||
|
||||
- [CLAUDE.md](CLAUDE.md) — `[[двойные скобки]` → нет такого файла
|
||||
- [snippets/invoice-template.md](snippets/invoice-template.md) — `[[projects/dttb/znamenskoye-log.md]` → нет такого файла
|
||||
- [decisions/2026-04-30-niikn-culture-gov-fakeip-fix.md](decisions/2026-04-30-niikn-culture-gov-fakeip-fix.md) — `[[notes/govru-diagnosis]` → нет такого файла
|
||||
- [decisions/2026-05-02-apple-id-tj-via-residential-proxy.md](decisions/2026-05-02-apple-id-tj-via-residential-proxy.md) — `[[../snippets/clients/]` → нет такого файла
|
||||
- [decisions/2026-04-29-rustdesk-client-deployment-package.md](decisions/2026-04-29-rustdesk-client-deployment-package.md) — `[[../snippets/clients/]` → нет такого файла
|
||||
- [decisions/2026-04-28-niikn-uookn-sev-gov-fakeip-fix.md](decisions/2026-04-28-niikn-uookn-sev-gov-fakeip-fix.md) — `[[notes/govru-diagnosis]` → нет такого файла
|
||||
- [decisions/2026-04-30-openwrt-homelab-agh-podkop-chain.md](decisions/2026-04-30-openwrt-homelab-agh-podkop-chain.md) — `[[../claude-memory/podkop]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[таргет]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[notes/govru-diagnosis]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[../snippets/clients/]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[../claude-memory/podkop]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[../snippets/clients/]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[notes/govru-diagnosis]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[../../../knowledge-base/feedback_lxc_loadavg]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[feedback_finland_security]` → нет такого файла
|
||||
- [audit/2026-05-03-health.md](audit/2026-05-03-health.md) — `[[..]` → нет такого файла
|
||||
- [projects/dttb/rustdesk.md](projects/dttb/rustdesk.md) — `[[../../../knowledge-base/feedback_lxc_loadavg]` → нет такого файла
|
||||
- [projects/lipki/README.md](projects/lipki/README.md) — `[[../znamenskoye/]` → нет такого файла
|
||||
- [projects/lipki/README.md](projects/lipki/README.md) — `[[../znamenskoye/]` → нет такого файла
|
||||
- [projects/lipki/README.md](projects/lipki/README.md) — `[[../znamenskoye/]` → нет такого файла
|
||||
- [projects/sergey/README.md](projects/sergey/README.md) — `[[../znamenskoye/]` → нет такого файла
|
||||
- [projects/dttb/graphify-out/GRAPH_REPORT.md](projects/dttb/graphify-out/GRAPH_REPORT.md) — `[[_COMMUNITY_Community 0|` → нет такого файла
|
||||
- [projects/dttb/graphify-out/GRAPH_REPORT.md](projects/dttb/graphify-out/GRAPH_REPORT.md) — `[[_COMMUNITY_Community 1|` → нет такого файла
|
||||
- [projects/dttb/graphify-out/GRAPH_REPORT.md](projects/dttb/graphify-out/GRAPH_REPORT.md) — `[[_COMMUNITY_Community 2|` → нет такого файла
|
||||
- [projects/dttb/graphify-out/GRAPH_REPORT.md](projects/dttb/graphify-out/GRAPH_REPORT.md) — `[[_COMMUNITY_Community 3|` → нет такого файла
|
||||
- [snippets/clients/yaroslav-amnezia-setup.md](snippets/clients/yaroslav-amnezia-setup.md) — `[[feedback_finland_security]` → нет такого файла
|
||||
195
scripts/kb-objects-audit.py
Normal file
195
scripts/kb-objects-audit.py
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
"""kb-objects-audit — еженедельный health-check vault'а.
|
||||
|
||||
Проверки:
|
||||
1. Каждый онлайн-netbird-пир имеет привязку к проекту (через aliases в frontmatter
|
||||
или собственную карточку). Orphans = клиентские машины без описания.
|
||||
2. Каждый projects/<dir>/README.md имеет валидный frontmatter (type, status,
|
||||
aliases как минимум).
|
||||
3. Битые wiki-ссылки `[[...]]` в vault'е (указывают в небытие).
|
||||
4. Дубли по нечёткому совпадению заголовков (опасные близнецы).
|
||||
|
||||
Output: audit/YYYY-MM-DD-objects-audit.md (каждый запуск перезаписывает дневной).
|
||||
|
||||
Запуск ручной или cron weekly:
|
||||
cd ~/knowledge-base && python3 scripts/kb-objects-audit.py
|
||||
"""
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
VAULT = Path(__file__).resolve().parent.parent
|
||||
OBJECTS_MAP = VAULT / "audit/objects-map.json"
|
||||
|
||||
REQUIRED_FRONTMATTER_KEYS = ["type", "status"]
|
||||
PROJECT_FRONTMATTER_KEYS = ["type", "status", "aliases"]
|
||||
|
||||
|
||||
def load_map() -> list:
|
||||
if not OBJECTS_MAP.exists():
|
||||
sys.exit(f"FATAL: нет {OBJECTS_MAP.relative_to(VAULT)} — запусти scripts/kb-objects-map.py сначала")
|
||||
return json.loads(OBJECTS_MAP.read_text())
|
||||
|
||||
|
||||
def parse_fm(text: str) -> dict:
|
||||
m = re.match(r"^---\n(.+?)\n---\n", text, re.S)
|
||||
if not m:
|
||||
return {}
|
||||
fm = {}
|
||||
for line in m.group(1).splitlines():
|
||||
if ":" not in line or line.startswith("#"):
|
||||
continue
|
||||
k, _, v = line.partition(":")
|
||||
fm[k.strip()] = v.strip()
|
||||
return fm
|
||||
|
||||
|
||||
def find_md_files() -> list[Path]:
|
||||
out = []
|
||||
for p in VAULT.rglob("*.md"):
|
||||
rel = p.relative_to(VAULT)
|
||||
if any(part.startswith(".") for part in rel.parts):
|
||||
continue
|
||||
if rel.parts[0] in ("audit",):
|
||||
# audit-отчёты сами не аудируются
|
||||
if "archive" in rel.parts:
|
||||
continue
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
def check_project_frontmatter(objects: list) -> list[str]:
|
||||
issues = []
|
||||
for o in objects:
|
||||
if o["type"] != "project" or not o["file"]:
|
||||
continue
|
||||
path = VAULT / o["file"]
|
||||
if not path.exists():
|
||||
issues.append(f"- `{o['id']}`: file missing — `{o['file']}`")
|
||||
continue
|
||||
fm = parse_fm(path.read_text())
|
||||
missing = [k for k in PROJECT_FRONTMATTER_KEYS if k not in fm]
|
||||
if missing:
|
||||
issues.append(f"- `{o['id']}`: frontmatter missing {missing} — `{o['file']}`")
|
||||
return issues
|
||||
|
||||
|
||||
def check_broken_wikilinks(files: list[Path]) -> list[tuple[str, str, str]]:
|
||||
"""Возвращает [(source, link, reason)] для битых [[...]] ссылок."""
|
||||
issues = []
|
||||
all_basenames = {f.stem for f in files}
|
||||
all_relpaths = {str(f.relative_to(VAULT)).replace(".md", "") for f in files}
|
||||
pat = re.compile(r"\[\[([^\]\|#]+?)(?:\||#|\])")
|
||||
for f in files:
|
||||
try:
|
||||
text = f.read_text()
|
||||
except Exception:
|
||||
continue
|
||||
for m in pat.finditer(text):
|
||||
target = m.group(1).strip().rstrip("/")
|
||||
if not target:
|
||||
continue
|
||||
# Allow absolute by relpath, or basename match, or relative-resolved
|
||||
if target in all_relpaths:
|
||||
continue
|
||||
base = target.split("/")[-1]
|
||||
if base in all_basenames:
|
||||
continue
|
||||
# try relative to source
|
||||
src_dir = f.parent.relative_to(VAULT)
|
||||
resolved = str(src_dir / target).replace("./", "")
|
||||
if resolved in all_relpaths:
|
||||
continue
|
||||
issues.append((str(f.relative_to(VAULT)), m.group(0), "→ нет такого файла"))
|
||||
return issues
|
||||
|
||||
|
||||
def check_orphans(objects: list) -> list[dict]:
|
||||
return [o for o in objects if o["type"] == "netbird-only"
|
||||
and o["netbird_peers"] and o["netbird_peers"][0].get("online", True)]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
objects = load_map()
|
||||
files = find_md_files()
|
||||
|
||||
fm_issues = check_project_frontmatter(objects)
|
||||
orphans = check_orphans(objects)
|
||||
wiki_issues = check_broken_wikilinks(files)
|
||||
|
||||
# счётчики
|
||||
n_proj = sum(1 for o in objects if o["type"] == "project")
|
||||
n_proj_with_fm = n_proj - len(fm_issues)
|
||||
n_orphan_online = len(orphans)
|
||||
n_wiki_broken = len(wiki_issues)
|
||||
score = len(fm_issues) * 5 + n_orphan_online * 2 + n_wiki_broken * 3
|
||||
today = date.today().isoformat()
|
||||
out = VAULT / f"audit/{today}-objects-audit.md"
|
||||
|
||||
md = [
|
||||
"---",
|
||||
f"date: {today}",
|
||||
"type: audit",
|
||||
"source: scripts/kb-objects-audit.py",
|
||||
"tags: [audit, objects, frontmatter, links]",
|
||||
f"score: {score}",
|
||||
"---",
|
||||
"",
|
||||
f"# KB objects audit — {today}",
|
||||
"",
|
||||
f"**Score (меньше = лучше): `{score}`**",
|
||||
"",
|
||||
f"- Проектов с frontmatter: **{n_proj_with_fm}/{n_proj}** ({len(fm_issues)} проблем)",
|
||||
f"- NetBird online-пиров без проектной карточки: **{n_orphan_online}**",
|
||||
f"- Битых wiki-ссылок `[[...]]`: **{n_wiki_broken}**",
|
||||
"",
|
||||
]
|
||||
|
||||
md.extend(["## Frontmatter в projects/", ""])
|
||||
if fm_issues:
|
||||
md.extend(fm_issues)
|
||||
else:
|
||||
md.append("✅ все проекты имеют валидный frontmatter")
|
||||
md.append("")
|
||||
|
||||
md.extend(["## Online netbird-пиры без проектной карточки",
|
||||
"",
|
||||
"Эти пиры онлайн в NetBird, но не привязаны ни к одной projects/-странице. ",
|
||||
"Бот не сможет ответить «найди X» осмысленно — нет файла или alias.",
|
||||
"",
|
||||
"Лечение: либо создать stub в `projects/<slug>/README.md` (см. `projects/lipki/` как образец), ",
|
||||
"либо добавить имя пира как полную строку в `aliases` подходящего проекта.",
|
||||
"",
|
||||
"| NetBird-имя | IP | OS | Город |",
|
||||
"|---|---|---|---|"])
|
||||
if orphans:
|
||||
for o in orphans:
|
||||
p = o["netbird_peers"][0]
|
||||
md.append(f"| `{p['name']}` | {p['ip']} | {p.get('os','')} | {p.get('city','')} |")
|
||||
else:
|
||||
md.append("| — | — | — | — |")
|
||||
md.append("")
|
||||
md.append("✅ все онлайн-пиры покрыты")
|
||||
md.append("")
|
||||
|
||||
md.extend(["## Битые wiki-ссылки", ""])
|
||||
if wiki_issues:
|
||||
for src, link, reason in wiki_issues[:50]:
|
||||
md.append(f"- [{src}]({src}) — `{link}` {reason}")
|
||||
if len(wiki_issues) > 50:
|
||||
md.append(f"- ... ещё {len(wiki_issues)-50} (truncated до 50)")
|
||||
else:
|
||||
md.append("✅ битых ссылок не найдено")
|
||||
md.append("")
|
||||
|
||||
out.write_text("\n".join(md))
|
||||
print(f"Wrote {out.relative_to(VAULT)} (score={score})")
|
||||
print(f" frontmatter issues: {len(fm_issues)}")
|
||||
print(f" orphan online peers: {n_orphan_online}")
|
||||
print(f" broken wiki links: {n_wiki_broken}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user