Initial commit: Hermes skill file-tree-catalog

This commit is contained in:
estorozhenko
2026-09-06 13:51:19 +00:00
commit be3ae18be7
3 changed files with 217 additions and 0 deletions
+34
View File
@@ -0,0 +1,34 @@
---
name: file-tree-catalog
description: Catalog a project/file tree into Markdown programmatically.
---
# File-Tree Catalog
Create a human-navigable Markdown catalog of a large local file tree (engineering project archives, design-document sets, media libraries). This is **mechanical filesystem work — do it with Python, do NOT burn tokens on an LLM**. Deliverables: a `КАТАЛОГ_проекта.md` in the root of the tree.
## When to use
- User asks to "каталогизировать", "сделать каталог/структуру/содержимое" of a folder, "понять что лежит в проекте X".
- User hands you a path to an archive of documents (СКС/ЛВС/КТСБ drawings, PDF sets, etc.) and wants structure + contents overview.
## Workflow
1. **Confirm the target** (what kind of catalog) with one `clarify` if ambiguous — but structural cataloging is usually unambiguous: structure + contents. Don't over-ask.
2. **Enumerate** the tree with `execute_code` (Python, not shell): walk `os.walk`, collect extensions, sizes, counts. Get a feel for scale first (files, GB, top-level dirs, distribution by extension) before building the document.
3. **Build the catalog procedurally** (see bundled script). Key decisions:
- Group by immediate subdirectories; recurse 2-3 levels max so the doc stays readable.
- Summarize `note+dwg/` (or similar bulk design subfolders) as `N файлов` instead of listing 50+ CAD files.
- Tag each file with its *role* derived from extension + naming: PDF="итоговый комплект / итоговый PDF", DOCX="список изменений", XLSX="замечания/ответы", TXT="изменения".
- Prepend an **Общая статистика** section (total files, total size, per-extension counts) and a **Состав проекта** section (what the top-level systems/objects are).
4. **Write** the `.md` into the tree root with `open(out,"w")`. Report the absolute path.
## Pitfalls
- **Don't use an LLM for the catalog itself** — building a `dict`/tree + markdown join is deterministic and faster. Reserve a local model only for *semantic* additions (expanding abbreviations), and only if it actually answers.
- **Don't fabricate expansions of unknown codes.** If a шифр/abbreviation (e.g. `Н-КВ`) isn't verifiable, state the *fact* ("belongs to the Кубань-Вино object") and explicitly note it's unverified — do NOT invent "новый корпус" etc.
- **Filter service/junk files** or the doc drowns. Consistent engineering-archive junk on this host: `Thumbs.db`, `~$…` (Office lock files), `.dwl`/`.dwl2` (AutoCAD locks), `.bak`, `plot.log`, `.db`. Count them separately in stats but omit from the body.
- **Ordinal/numbering**: sort dirs with `key=str.lower` for natural locale ordering (КТСБ.1 < КТСБ.2, зоны in order).
- Be careful writing the markdown with `%`-format strings or plain concatenation — **nested f-strings with quotes inside a list comprehension raise a SyntaxError**; use `%`-formatting or build lines imperatively.
- Optional user-request: "use the local model to save tokens" — for mechanical cataloging the honest answer is "no model needed"; say so rather than calling ollama pointlessly. Local qwen3 on this host returns empty `content` (reasoning-only replies), so don't count on it for prose.
## Files
- `scripts/build_catalog.py` — reusable generator (stats + tree + role tagging + junk filter). Edit the constant `ROOT` and run; adapt `maxdepth`/`junk` sets as needed.
- `references/engineering-doc-archive.md` — conventions and domain notes for Russian design/СКС-ЛВС-КТСБ document archives.
+48
View File
@@ -0,0 +1,48 @@
# Russian design-document archive conventions (СКС / ЛВС / КТСБ)
Domain notes from cataloging the "Винный город" project archive. Useful for
sensing what files mean by name and for expanding systems/objects in catalogs.
## Systems (шифр prefix)
- **СКС** — структурированная кабельная система
- **ЛВС** — локально-вычислительная сеть
- **КТСБ** — комплекс технических средств безопасности (СКУД, видеонаблюдение, охранная сигнализация и т.п.)
- **ГП2 / ГП** — генплан (used in «Уличный бар» folder)
## Document шифр pattern
`1289-1-1-Р-<СИСТЕМА>.<объект>[.подраздел]_<вариант>.pdf` where:
- `1289-1-1-Р` — project / series / stage (Р = «рабочая документация»)
- `<объект>` — building/zone number (1, 2, 3, 4, 6, 7, 9; note: 5 and 8 have no folders — don't assume they exist)
- `.1` suffix — sub-division of a system/zone (СКС.1 vs СКС.1.1, ЛВС.7 vs ЛВС.7.1)
- Optional `Н-КВ` after project: `1289-1-1-Р-Н-КВ-СКС.7` — belongs to the **Кубань-Вино** object subset.
## Companion files (role by suffix / extension)
- `*_Изменения_Изм.N_От <date>.docx` — change/revision list (docx)
- `*_Замечания*_Ответ We-On.xlsx` — review comments + contractor response (xlsx)
- `*_Изменения.txt` — plain-text change notes
- `*_ЖАН.pdf` — отдельный жан/выпуск (a ЖАН issue — versioned sub-release of the doc)
- `*_Лист регистрации изменения.docx` — change-registration sheet
- `*_Обложка+ТЛ*.docx` — cover + title/technical sheet
- Bulk CAD+schema folder **`note+dwg/`** — contains per-sheet drawings:
- `*_л.N.dwg` — leaf/sheet N (single) or `л.2-5.dwg` (range)
- `*_л.1_ОД.dwg` — ОД (общая/общие данные?)
- `*_СО*.docx` — спецификация оборудования (equipment schedule)
- `*_КЖ.doc/.docx` — ?
- `*_Зд1/Зд2.xlsx`, `*_ТП1/ТП2/ТП3.xlsx` — данные по зданиям / техпроцессу (Excel)
- `*_П1..П7.dwg/.pdf/.docx` — планы (floor plans) various
- `*_РР.dwg`, `*_И1.dwg` — расчётные/исполнительные чертежи
## Contractors (in document headers)
- **WE-ON** — ГИП Рыкова
- **Велесстрой** — ГИП Кузнецов
## Folder layout
`<СИСТЕМА.N>/<YYYY.MM.DD>_Rev.XX/` — one folder per revision peak. Top-level
also may contain special objects: **Кубань-Вино** (systems of zone 7 done for a
separate client), **Уличный бар**.
## Local-model note
Local `qwen3:8b` on this host answers ONLY via `reasoning_content`; `content`
comes back empty even with `think:false` / `options.num_think:0` attempts. Do
not rely on it for generated prose. For semantic expansion of abbreviations,
prefer stating verified facts over inventing meaning.
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""Build a navigable Markdown catalog (КАТАЛОГ_проекта.md) of a large file tree.
Adapt ROOT (and optionally junk/MAP/docs) for each project, then run:
python3 build_catalog.py
Design rules (from real use):
- Mechanical, deterministic: no LLM for the structure/stats.
- Recurse top-level dirs + their immediate children only (maxdepth ~2) so the
doc stays readable. Summarize bulk design subfolders (note+dwg) as "N файлов".
- Tag each file's role from extension (+ naming conventions), Russian labels.
- Filter service/junk files from the body but count them in stats.
- Sort dirs with key=str.lower for natural locale ordering.
Reproduce the exact structure used for the "Винный город" СКС/ЛВС/КТСБ archive:
<root>/
<СИСТЕМА.N>/ e.g. КТСБ.1, ЛВС.2, СКС.3
<дата_Rev.XX>/ e.g. 2026.05.18_Rev.01
<шифр>.pdf итоговый комплект
*_Изменения_*.docx список изменений
*_Замечания*.xlsx замечания/ответы
note+dwg/ bulk drawings + spec files (collapse to count)
"""
import os
import sys
import datetime
import collections
# >>> EDIT THESE for a new project >>>
ROOT = "/mnt/vinogorod/ИТ/1. Винный город/Проекты/2026.05.22_Актуальные комплекты СКС, ЛВС, КТСБ"
# subfolders to collapse to a count (engine+Cad sets)
BULK_DIR_NAMES = ("note+dwg", "note", "dwg")
# service files to exclude from the body (but still counted in the stats line)
JUNK_NAMES = {"thumbs.db", "plot.log", "desktop.ini"}
JUNK_EXTS = {".db", ".dwl", ".dwl2", ".bak"}
# <<< END EDIT <<<
# numeric suffix so .1/.2 sort after the base in locale order
STRIP = str.lstrip # alias to avoid binding w/ layer in comprehension below
def collect(base):
"""Return {name: ("FILE", ext_upper) | ("DIR", file_count)} for one level."""
out = {}
for name in sorted(os.listdir(base), key=str.lower):
p = os.path.join(base, name)
if name.lower() in JUNK_NAMES or name.startswith("~$"):
continue
if os.path.isdir(p):
out[name] = ("DIR", sum(len(x) for _, _, x in os.walk(p)))
else:
ext = os.path.splitext(name)[1].lstrip(".").upper()
if ext in {e.lstrip(".").upper() for e in JUNK_EXTS}:
continue
out[name] = ("FILE", ext)
return out
def describe(ext):
return {
"PDF": "итоговый комплект PDF",
"DOCX": "список изменений (docx)",
"XLSX": "замечания/ответы (xlsx)",
"TXT": "изменения (txt)",
"DWG": "чертёж AutoCAD (dwg)",
"DOC": "документ (doc)",
"LOG": "лог",
"MD": "каталог/описание (md)",
"BAK": "резервная копия (bak)",
}.get(ext, ext)
def build(root):
# --- stats ---
ext_count = collections.Counter()
total_files = total_size = junk = 0
for dirpath, _, filenames in os.walk(root):
for f in filenames:
ext = os.path.splitext(f)[1].lower() or "(нет)"
ext_count[ext] += 1
total_files += 1
try:
total_size += os.path.getsize(os.path.join(dirpath, f))
except OSError:
pass
if (f.startswith("~$") or f.lower() in JUNK_NAMES
or ext in JUNK_EXTS):
junk += 1
L = ["# Каталог проекта", "",
"**Путь:** `%s`" % root, "",
"**Сформирован:** %s" % datetime.date.today().isoformat(), "",
"## Общая статистика", ""]
L.append("- Всего файлов: **%d** (~%.2f ГБ)" % (total_files, total_size / 1024**3))
parts = []
for e, c in ext_count.most_common():
parts.append("**%s**: %d" % (e.lstrip(".").upper() or "без расширения", c))
L.append("- По типам: %s" % ", ".join(parts))
L.append("- Из них служебных (Thumbs.db, ~$, .dwl/.dwl2/.bak): %d (не включены)" % junk)
L += ["", "## Структура и содержимое", ""]
for top in sorted(os.listdir(root), key=str.lower):
topp = os.path.join(root, top)
if not os.path.isdir(topp):
continue
L.append("### %s" % top)
L.append("")
for name, (typ, val) in collect(topp).items():
if typ == "DIR":
if name.lower() in BULK_DIR_NAMES:
L.append("- **`%s/`** — %d файлов (чертежи + спецификации)" % (name, val))
else:
L.append("**%s**" % name)
L.append("")
for sub, (styp, sval) in collect(os.path.join(topp, name)).items():
if styp == "DIR":
if sub.lower() in BULK_DIR_NAMES:
L.append("- **`%s/`** — %d файлов (чертежи + спецификации)" % (sub, sval))
else:
L.append("- **`%s/`** — %d файлов" % (sub, sval))
else:
L.append("- `%s` — %s" % (sub, describe(sval)))
L.append("")
else:
L.append("- `%s` — %s" % (name, describe(val)))
return "\n".join(L)
if __name__ == "__main__":
root = sys.argv[1] if len(sys.argv) > 1 else ROOT
text = build(root)
out = os.path.join(root, "КАТАЛОГ_проекта.md")
with open(out, "w") as fh:
fh.write(text)
print("OK ->", out, os.path.getsize(out), "байт")