code-server auto-sync 2026-04-18T17:20:01+00:00
This commit is contained in:
196
scripts/kb-add-frontmatter.py
Normal file
196
scripts/kb-add-frontmatter.py
Normal file
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add frontmatter to .md files that lack it. One-off agent-loop fix."""
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
VAULT = Path(__file__).resolve().parent.parent
|
||||
|
||||
FILES = """
|
||||
decisions/2026-04-16-unifi-migration-peredelki.md
|
||||
decisions/2026-03-03-mailserver-setup-scenario.md
|
||||
decisions/2026-03-04-matrix-niikn-setup.md
|
||||
decisions/2026-04-14-niikn-openwrt-awg-fix.md
|
||||
decisions/2026-04-17-peredelki-podkop-stability-fix.md
|
||||
decisions/2026-02-26-clawdbot-129-cliproxy-fix.md
|
||||
decisions/2026-04-14-openclaw-claude-code-pipeline.md
|
||||
decisions/2026-04-17-code-server-upgrade.md
|
||||
notes/2026-02-26-knowledge-base-setup.md
|
||||
notes/2026-02-26-session-summary.md
|
||||
notes/2026-02-26-claude-code-session-clawdbot-fix.md
|
||||
notes/2026-02-26-full-session-log.md
|
||||
projects/unresolved-issues.md
|
||||
projects/nextcloud.md
|
||||
projects/infrastructure-overview.md
|
||||
projects/homelab-proxmox.md
|
||||
projects/video-surveillance.md
|
||||
projects/bitrix-sites.md
|
||||
projects/clawdbot-bots.md
|
||||
projects/all-projects-summary.md
|
||||
projects/dttb/npm-homelab.md
|
||||
projects/dttb/nextcloud.md
|
||||
projects/dttb/proxmox-inventory.md
|
||||
projects/dttb/server1c.md
|
||||
projects/dttb/agentdvr-home.md
|
||||
projects/dttb/memory-inventory.md
|
||||
projects/dttb/video-surveillance-report.md
|
||||
projects/dttb/openclaw.md
|
||||
projects/dttb/credentials.md
|
||||
projects/dttb/oleg-agent.md
|
||||
projects/dttb/netbird-inventory.md
|
||||
projects/dttb/clawdbot.md
|
||||
projects/dttb/clawdbot-znam.md
|
||||
projects/dttb/spaceweb-dns.md
|
||||
projects/dttb/npm-proxy-hosts.md
|
||||
projects/dttb/gpu-passthrough.md
|
||||
projects/dttb/openwrt-router.md
|
||||
projects/dttb/gitea.md
|
||||
projects/dttb/mailcow-dttb.md
|
||||
projects/dttb/videonablyudenie-znam.md
|
||||
projects/dttb/matrix-homelab.md
|
||||
projects/dttb/homeassistant.md
|
||||
projects/dttb/network-topology.md
|
||||
projects/glavtorg/instruction-diana-rdp.md
|
||||
projects/niikn/mailcow.md
|
||||
projects/niikn/NIIKN-Infrastructure.md
|
||||
projects/niikn/groupfolders-migration.md
|
||||
projects/niikn/changelog.md
|
||||
projects/niikn/matrix.md
|
||||
projects/niikn/clawdbot-niikn.md
|
||||
projects/niikn/proxmox.md
|
||||
projects/niikn/NIIKN-ChangeLog.md
|
||||
projects/niikn/npm.md
|
||||
projects/niikn/mikrotik.md
|
||||
projects/niikn/openwrt-bypass.md
|
||||
projects/niikn/NC-Talk-Setup.md
|
||||
projects/niikn/vpn.md
|
||||
projects/mmfb/proxmox-inventory.md
|
||||
snippets/proxmox-console-quirks.md
|
||||
snippets/clawdbot-cliproxy-config.md
|
||||
claude-memory/servicedesk-dttb.md
|
||||
claude-memory/benelux.md
|
||||
claude-memory/nextcloud-dttb.md
|
||||
claude-memory/nvr-fix.md
|
||||
claude-memory/videonablyudenie.md
|
||||
claude-memory/znamenskoe-home.md
|
||||
claude-memory/niikn-nextcloud.md
|
||||
claude-memory/krasnogorsk.md
|
||||
claude-memory/mas-niikn.md
|
||||
claude-memory/MEMORY.md
|
||||
""".strip().splitlines()
|
||||
|
||||
TODAY = "2026-04-18"
|
||||
|
||||
|
||||
def file_date(rel_path: str, full: Path) -> str:
|
||||
name = full.name
|
||||
m = re.match(r"^(\d{4}-\d{2}-\d{2})", name)
|
||||
if m:
|
||||
return m.group(1)
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["git", "log", "--diff-filter=A", "--follow", "--format=%ad",
|
||||
"--date=short", "--", rel_path],
|
||||
cwd=VAULT, capture_output=True, text=True, timeout=10
|
||||
)
|
||||
lines = [l.strip() for l in r.stdout.splitlines() if l.strip()]
|
||||
if lines:
|
||||
return lines[-1]
|
||||
except Exception:
|
||||
pass
|
||||
return TODAY
|
||||
|
||||
|
||||
def type_for(rel_path: str) -> str:
|
||||
parts = rel_path.split("/")
|
||||
if parts[0] == "decisions":
|
||||
return "decision"
|
||||
if parts[0] == "notes":
|
||||
return "note"
|
||||
if parts[0] == "daily":
|
||||
return "daily"
|
||||
if parts[0] == "projects":
|
||||
return "project"
|
||||
if parts[0] == "snippets":
|
||||
return "reference"
|
||||
if parts[0] == "claude-memory":
|
||||
return "reference"
|
||||
if parts[0] == "templates":
|
||||
return "template"
|
||||
return "note"
|
||||
|
||||
|
||||
def tags_for(rel_path: str) -> list:
|
||||
parts = rel_path.split("/")
|
||||
tags = []
|
||||
if parts[0] == "decisions":
|
||||
tags.append("decision")
|
||||
elif parts[0] == "notes":
|
||||
tags.append("note")
|
||||
elif parts[0] == "snippets":
|
||||
tags.append("snippet")
|
||||
elif parts[0] == "claude-memory":
|
||||
tags.append("memory")
|
||||
if len(parts) > 1 and parts[0] == "projects":
|
||||
proj = parts[1]
|
||||
if proj in ("dttb", "niikn", "glavtorg", "mmfb", "krasnogorsk"):
|
||||
tags.append(proj)
|
||||
lower = rel_path.lower()
|
||||
if "nextcloud" in lower:
|
||||
tags.append("nextcloud")
|
||||
if "matrix" in lower:
|
||||
tags.append("matrix")
|
||||
if "proxmox" in lower:
|
||||
tags.append("proxmox")
|
||||
if "npm" in lower:
|
||||
tags.append("npm")
|
||||
if "dns" in lower:
|
||||
tags.append("dns")
|
||||
if "mailcow" in lower or "mail" in lower.split("/")[-1]:
|
||||
tags.append("mail")
|
||||
if "clawdbot" in lower or "openclaw" in lower:
|
||||
tags.append("bot")
|
||||
if "vpn" in lower or "openwrt" in lower or "netbird" in lower:
|
||||
tags.append("network")
|
||||
if "video" in lower or "surveillance" in lower or "camera" in lower or "agentdvr" in lower:
|
||||
tags.append("video")
|
||||
if not tags:
|
||||
tags = ["note"]
|
||||
seen = set()
|
||||
out = []
|
||||
for t in tags:
|
||||
if t not in seen:
|
||||
seen.add(t)
|
||||
out.append(t)
|
||||
return out
|
||||
|
||||
|
||||
def add_fm(rel_path: str):
|
||||
full = VAULT / rel_path
|
||||
if not full.is_file():
|
||||
print(f"MISSING: {rel_path}")
|
||||
return
|
||||
text = full.read_text()
|
||||
if text.startswith("---\n"):
|
||||
print(f"HAS-FM: {rel_path}")
|
||||
return
|
||||
d = file_date(rel_path, full)
|
||||
t = type_for(rel_path)
|
||||
tags = tags_for(rel_path)
|
||||
fm = (
|
||||
"---\n"
|
||||
f"date: {d}\n"
|
||||
f"type: {t}\n"
|
||||
f"tags: [{', '.join(tags)}]\n"
|
||||
"---\n\n"
|
||||
)
|
||||
full.write_text(fm + text)
|
||||
print(f"FIXED: {rel_path} (date={d}, type={t}, tags={tags})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for line in FILES:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
add_fm(line)
|
||||
47
scripts/kb-date-todos.py
Normal file
47
scripts/kb-date-todos.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Append (YYYY-MM-DD) to undated '- [ ]' lines. One-off agent-loop fix."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
VAULT = Path(__file__).resolve().parent.parent
|
||||
TODAY = "2026-04-18"
|
||||
|
||||
TARGETS = {
|
||||
"projects/niikn/changelog.md": TODAY,
|
||||
"projects/niikn/NIIKN-ChangeLog.md": TODAY,
|
||||
"projects/niikn/matrix.md": TODAY,
|
||||
"projects/dttb/nextcloud-talk-bot/README.md": TODAY,
|
||||
"daily/2026-04-19.md": "2026-04-19",
|
||||
"claude-memory/mas-niikn.md": TODAY,
|
||||
"decisions/2026-04-16-unifi-migration-peredelki.md": "2026-04-16",
|
||||
"projects/niikn/README.md": TODAY,
|
||||
"decisions/2026-04-14-openclaw-claude-code-pipeline.md": "2026-04-14",
|
||||
"projects/dttb/mailcow-dttb.md": TODAY,
|
||||
"daily/2026-04-17.md": "2026-04-17",
|
||||
"templates/daily-note.md": TODAY,
|
||||
}
|
||||
|
||||
DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}")
|
||||
TODO_RE = re.compile(r"^(\s*-\s*\[\s*\]\s*.*?)\s*$")
|
||||
|
||||
for rel, date in TARGETS.items():
|
||||
p = VAULT / rel
|
||||
if not p.is_file():
|
||||
print(f"MISSING: {rel}")
|
||||
continue
|
||||
lines = p.read_text().splitlines()
|
||||
fixed = 0
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
m = re.match(r"^\s*-\s*\[\s*\]\s+", line)
|
||||
if m and not DATE_RE.search(line):
|
||||
stripped = line.rstrip()
|
||||
new_lines.append(f"{stripped} ({date})")
|
||||
fixed += 1
|
||||
elif re.match(r"^\s*-\s*\[\s*\]\s*$", line):
|
||||
new_lines.append(f"{line.rstrip()} ({date})")
|
||||
fixed += 1
|
||||
else:
|
||||
new_lines.append(line)
|
||||
p.write_text("\n".join(new_lines) + ("\n" if lines and not lines[-1] == "" else ""))
|
||||
print(f"{rel}: {fixed} fixed")
|
||||
115
scripts/kb-list-issues.py
Normal file
115
scripts/kb-list-issues.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Temporary diagnostic — list all KB issues in full."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
VAULT = Path(__file__).resolve().parent.parent
|
||||
INCLUDE_DIRS = ['decisions', 'notes', 'projects', 'snippets', 'daily',
|
||||
'claude-memory', 'templates', 'scripts', 'audit']
|
||||
EXCLUDE = {'.git', '.obsidian', '.claude'}
|
||||
|
||||
files = []
|
||||
for d in INCLUDE_DIRS:
|
||||
root = VAULT / d
|
||||
if not root.exists():
|
||||
continue
|
||||
for p in root.rglob('*.md'):
|
||||
if any(part in EXCLUDE for part in p.parts):
|
||||
continue
|
||||
files.append(p)
|
||||
for name in ('CLAUDE.md', 'README.md'):
|
||||
p = VAULT / name
|
||||
if p.is_file():
|
||||
files.append(p)
|
||||
|
||||
def has_fm(txt):
|
||||
if not txt.startswith('---\n'):
|
||||
return False
|
||||
return bool(re.match(r'---\n(.*?\n)*?---\n', txt))
|
||||
|
||||
def strip_code(t):
|
||||
t = re.sub(r'```[\s\S]*?```', '', t)
|
||||
t = re.sub(r'`[^`\n]*`', '', t)
|
||||
return t
|
||||
|
||||
def wikilinks(t):
|
||||
out = []
|
||||
for m in re.finditer(r'\[\[([^\]]+)\]\]', strip_code(t)):
|
||||
tgt = m.group(1).split('|')[0].split('#')[0].strip()
|
||||
if tgt:
|
||||
out.append(tgt)
|
||||
return out
|
||||
|
||||
basenames = defaultdict(list)
|
||||
for p in files:
|
||||
basenames[p.stem].append(p)
|
||||
|
||||
def resolve_wl(target, from_file):
|
||||
tc = target.replace('.md', '')
|
||||
if tc.startswith(('.', '/')):
|
||||
try:
|
||||
r = (from_file.parent / tc).resolve()
|
||||
for c in (r.with_suffix('.md'), r):
|
||||
if c.is_file() and str(c).endswith('.md'):
|
||||
return c
|
||||
except Exception:
|
||||
pass
|
||||
g = VAULT / f'{tc}.md'
|
||||
if g.is_file():
|
||||
return g
|
||||
b = tc.rsplit('/', 1)[-1]
|
||||
if b in basenames:
|
||||
return basenames[b][0]
|
||||
return None
|
||||
|
||||
missing = []
|
||||
incoming = defaultdict(set)
|
||||
todos = defaultdict(list)
|
||||
|
||||
for f in files:
|
||||
txt = f.read_text(errors='ignore')
|
||||
rel = str(f.relative_to(VAULT))
|
||||
if f.name not in ('README.md', 'CLAUDE.md') and not has_fm(txt):
|
||||
missing.append(rel)
|
||||
for target in wikilinks(txt):
|
||||
r = resolve_wl(target, f)
|
||||
if r:
|
||||
incoming[r].add(f)
|
||||
for i, line in enumerate(txt.splitlines(), 1):
|
||||
if re.match(r'^\s*-\s*\[\s*\]\s+', line):
|
||||
if not re.search(r'\d{4}-\d{2}-\d{2}', line):
|
||||
todos[rel].append((i, line))
|
||||
|
||||
ORPHAN_OK = [
|
||||
re.compile(r'^daily/.*'),
|
||||
re.compile(r'^audit/.*'),
|
||||
re.compile(r'^templates/.*'),
|
||||
re.compile(r'^notes/claude/.*'),
|
||||
re.compile(r'^scripts/.*'),
|
||||
re.compile(r'^CLAUDE\.md$'),
|
||||
re.compile(r'^README\.md$'),
|
||||
]
|
||||
def is_ok(rel):
|
||||
return any(p.match(rel) for p in ORPHAN_OK)
|
||||
|
||||
orphans = []
|
||||
for f in files:
|
||||
if f not in incoming:
|
||||
rel = str(f.relative_to(VAULT))
|
||||
if not is_ok(rel):
|
||||
orphans.append(rel)
|
||||
|
||||
print('=== MISSING FRONTMATTER ({}) ==='.format(len(missing)))
|
||||
for x in missing:
|
||||
print(x)
|
||||
print()
|
||||
print('=== ORPHANS ({}) ==='.format(len(orphans)))
|
||||
for x in orphans:
|
||||
print(x)
|
||||
print()
|
||||
print('=== UNDATED TODOS ({}) ==='.format(sum(len(v) for v in todos.values())))
|
||||
for f, lines in sorted(todos.items(), key=lambda x: -len(x[1])):
|
||||
print('FILE:', f, '({})'.format(len(lines)))
|
||||
for ln, line in lines:
|
||||
print(' L{}: {}'.format(ln, line.strip()))
|
||||
Reference in New Issue
Block a user