mirror of
https://gitverse.ru/kpa39l/file-tree-catalog.git
synced 2026-09-29 09:15:06 +00:00
Initial commit: Hermes skill file-tree-catalog
This commit is contained in:
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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), "байт")
|
||||||
Reference in New Issue
Block a user