library-manager: restore lost FBD thermal-resistance change; changelog now cumulative

main
library-manager 2026-07-10 11:01:28 +00:00
parent b69a638875
commit 8781deb852
No known key found for this signature in database
6 changed files with 80 additions and 17 deletions

View File

@ -85,6 +85,22 @@ nothing.
Run these in order. Each `python`/`bash` command is a helper in `scripts/`. Run these in order. Each `python`/`bash` command is a helper in `scripts/`.
### 0. Sync the skill state from Gitea first — always
The skill's state (the template, the per-typeid versions, and the changelog) lives in the
**skill repo**, and it grows over time. A fresh install/session starts from the packaged v1
state, so if you don't sync first, a second template change wouldn't build on the first — the
versions would restart at v1 and the changelog would look like it only holds the latest change.
**So begin every run by pulling the current state:**
```bash
python scripts/gitea_components.py pull-skill
```
This copies `template.xlsx`, `versions.json`, and `CHANGELOG.xlsx` from the skill repo into the
local skill, so version bumps continue correctly (v2 → v3 → …) and the changelog stays
**cumulative from the very first change**.
### 1. Duplicate check first — before any real work ### 1. Duplicate check first — before any real work
The part's presence is keyed on **MPN + make** (typeid not known yet). If it already exists, The part's presence is keyed on **MPN + make** (typeid not known yet). If it already exists,
@ -239,10 +255,13 @@ typeid's template/version changes, one row is appended with columns
**Date | Typeid | Skill Version | Template Version | Description** — the version columns hold **Date | Typeid | Skill Version | Template Version | Description** — the version columns hold
the new versions, and Description is your note (or the parameter(s) added if you gave none). the new versions, and Description is your note (or the parameter(s) added if you gave none).
The changelog lives in the **skill repo in Gitea** as well. When you push (via `push-skill`), The changelog lives in the **skill repo in Gitea** as well, and it is **cumulative from the
the new local rows are **merged onto the changelog already in Gitea** — appended, never first change onward**. Two things keep it that way: at the start of a run `pull-skill` (step 0)
overwritten — so the Gitea copy is the growing, authoritative history across machines and brings the current changelog down so a new change appends to the full history, and on push
sessions, and the merged file is copied back locally so the two stay in sync. `push-skill` **merges** the new local rows onto the changelog already in Gitea — appended,
never overwritten. So the Gitea copy is the growing, authoritative history across machines and
sessions; the merged file is copied back locally so the two stay in sync. If you ever see the
Gitea changelog with only the latest change, it means step 0 (`pull-skill`) was skipped.
## Backfilling existing parts ## Backfilling existing parts

Binary file not shown.

Binary file not shown.

View File

@ -156,8 +156,8 @@
"template_version": 1 "template_version": 1
}, },
"FBD": { "FBD": {
"skill_version": 1, "skill_version": 2,
"template_version": 1 "template_version": 2
}, },
"FFC": { "FFC": {
"skill_version": 1, "skill_version": 1,

View File

@ -3,7 +3,7 @@
# Use a token scoped to these repos (repository: write) and rotate periodically. # Use a token scoped to these repos (repository: write) and rotate periodically.
GIT_HOST=gitea.vecmocon.com GIT_HOST=gitea.vecmocon.com
GIT_USER=nitishKumar GIT_USER=nitishKumar
GIT_TOKEN=451bff1dc32202cbc0a371f8e5645079466d2120 GIT_TOKEN=
# Target repos — TWO now (set these to the exact repo names on your Gitea): # Target repos — TWO now (set these to the exact repo names on your Gitea):
# SKILL_REPO : holds this skill's own files (SKILL.md, scripts, assets, ...). # SKILL_REPO : holds this skill's own files (SKILL.md, scripts, assets, ...).
# LIBRARY_REPO : holds components, one folder per Class (Diode, IC, ...); inside each, # LIBRARY_REPO : holds components, one folder per Class (Diode, IC, ...); inside each,

View File

@ -43,10 +43,18 @@ nothing is written.
""" """
import argparse, os, re, shutil, subprocess, sys, tempfile import argparse, os, re, shutil, subprocess, sys, tempfile
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from common import class_folder, load_taxonomy, mpn_make_prefix from common import (class_folder, load_taxonomy, mpn_make_prefix,
SKILL_ROOT, TEMPLATE_XLSX, VERSIONS_JSON, CHANGELOG_XLSX)
CFG_DEFAULT = os.path.join(os.path.dirname(__file__), "..", "config", "gitea.env") CFG_DEFAULT = os.path.join(os.path.dirname(__file__), "..", "config", "gitea.env")
# The skill's stateful files (accumulate over time) — kept in the SKILL repo and pulled at the
# start of a run so template/version/changelog changes always build on Gitea's latest state.
# Paths are derived from common so the writer (append_parameter) and the sync can never drift.
STATE_FILES = [os.path.relpath(p, SKILL_ROOT)
for p in (TEMPLATE_XLSX, VERSIONS_JSON, CHANGELOG_XLSX)]
CHANGELOG_REL = os.path.relpath(CHANGELOG_XLSX, SKILL_ROOT)
# ------------------------------------------------------------------ gitea plumbing # ------------------------------------------------------------------ gitea plumbing
@ -199,8 +207,6 @@ def cmd_push_part(env, args):
# ------------------------------------------------------------------ skill repo + changelog # ------------------------------------------------------------------ skill repo + changelog
CHANGELOG_REL = os.path.join("assets", "CHANGELOG.xlsx")
def _changelog_rows(path): def _changelog_rows(path):
"""(workbook, sheet, [row-tuples]) for a changelog xlsx; ([]) if the file is absent.""" """(workbook, sheet, [row-tuples]) for a changelog xlsx; ([]) if the file is absent."""
@ -215,11 +221,12 @@ def _changelog_rows(path):
def merge_changelog(local_path, repo_path): def merge_changelog(local_path, repo_path):
"""Append the local changelog's rows onto the repo's existing changelog (union, order """Union the local changelog's rows into the repo's changelog and keep the result sorted by
preserved: repo history first, then any local rows not already there). This is what keeps date (chronological, oldest first). This keeps Gitea's changelog as the growing source of
Gitea's changelog as the growing source of truth instead of being overwritten. Writes the truth appended, never overwritten and always in order regardless of which machine
merged file to repo_path and returns how many new rows were added.""" pushed when. Writes the merged file to repo_path and returns how many new rows were added."""
import shutil import shutil
from openpyxl.styles import Border, Side, Alignment
if not os.path.exists(local_path): if not os.path.exists(local_path):
return 0 return 0
if not os.path.exists(repo_path): # repo has none yet -> seed from local if not os.path.exists(repo_path): # repo has none yet -> seed from local
@ -230,12 +237,24 @@ def merge_changelog(local_path, repo_path):
repo_wb, repo_ws, repo_rows = _changelog_rows(repo_path) repo_wb, repo_ws, repo_rows = _changelog_rows(repo_path)
_, _, local_rows = _changelog_rows(local_path) _, _, local_rows = _changelog_rows(local_path)
seen = set(repo_rows) seen = set(repo_rows)
merged = list(repo_rows)
added = 0 added = 0
for row in local_rows: for row in local_rows:
if row not in seen: if row not in seen:
repo_ws.append(list(row)) merged.append(row)
seen.add(row) seen.add(row)
added += 1 added += 1
merged.sort(key=lambda r: r[0]) # chronological by Date (column A)
if repo_ws.max_row > 1: # rewrite data rows, keep the header
repo_ws.delete_rows(2, repo_ws.max_row - 1)
s = Side(style="thin", color="BFBFBF")
border = Border(left=s, right=s, top=s, bottom=s)
for row in merged:
repo_ws.append(list(row))
rr = repo_ws.max_row
for c in range(1, 6):
repo_ws.cell(rr, c).border = border
repo_ws.cell(rr, c).alignment = Alignment(vertical="center", wrap_text=(c == 5), horizontal="left")
repo_wb.save(repo_path) repo_wb.save(repo_path)
return added return added
@ -281,6 +300,27 @@ def cmd_push_skill(env, args):
commit_push(tmp, env.get("GIT_TOKEN", ""), args.message or "library-manager: sync skill files + changelog") commit_push(tmp, env.get("GIT_TOKEN", ""), args.message or "library-manager: sync skill files + changelog")
def cmd_pull_skill(env, args):
"""Pull the skill's stateful files (template.xlsx, versions.json, CHANGELOG.xlsx) from the
SKILL repo into the local skill BEFORE making changes. This is what makes template/version
changes cumulative: without it, a fresh run starts from the packaged v1 state, so a second
change wouldn't build on the first (versions would restart at v1 and the changelog would
look like it only has the latest change). Run this at the start of every run."""
src = os.path.abspath(args.src)
tmp = tempfile.mkdtemp(prefix="skillpull_")
clone(env, tmp, env["SKILL_REPO"])
pulled = []
for rel in STATE_FILES:
s = os.path.join(tmp, rel)
d = os.path.join(src, rel)
if os.path.exists(s):
os.makedirs(os.path.dirname(d), exist_ok=True)
shutil.copy2(s, d)
pulled.append(rel)
print("pulled from skill repo: " + (", ".join(pulled) if pulled else
"nothing yet (skill repo has no template/version/changelog state)"))
def main(): def main():
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument("--config", default=CFG_DEFAULT) ap.add_argument("--config", default=CFG_DEFAULT)
@ -308,10 +348,14 @@ def main():
help="skill directory to push (defaults to this skill)") help="skill directory to push (defaults to this skill)")
p.add_argument("--message") p.add_argument("--message")
p = sub.add_parser("pull-skill")
p.add_argument("--src", default=os.path.join(os.path.dirname(__file__), ".."),
help="skill directory to sync into (defaults to this skill)")
args = ap.parse_args() args = ap.parse_args()
env = load_env(args.config) env = load_env(args.config)
needs = {"GIT_HOST", "GIT_TOKEN"} needs = {"GIT_HOST", "GIT_TOKEN"}
if args.cmd == "push-skill": if args.cmd in ("push-skill", "pull-skill"):
needs.add("SKILL_REPO") needs.add("SKILL_REPO")
elif args.cmd in ("check-mpn", "checkout", "commit-push", "push-part") or \ elif args.cmd in ("check-mpn", "checkout", "commit-push", "push-part") or \
(args.cmd in ("list-type", "place-part") and not getattr(args, "local", None) and not getattr(args, "root", None)): (args.cmd in ("list-type", "place-part") and not getattr(args, "local", None) and not getattr(args, "root", None)):
@ -321,7 +365,7 @@ def main():
sys.exit(f"missing {k} in env/config") sys.exit(f"missing {k} in env/config")
{"check-mpn": cmd_check_mpn, "checkout": cmd_checkout, "list-type": cmd_list_type, {"check-mpn": cmd_check_mpn, "checkout": cmd_checkout, "list-type": cmd_list_type,
"place-part": cmd_place_part, "commit-push": cmd_commit_push, "push-part": cmd_push_part, "place-part": cmd_place_part, "commit-push": cmd_commit_push, "push-part": cmd_push_part,
"push-skill": cmd_push_skill}[args.cmd](env, args) "push-skill": cmd_push_skill, "pull-skill": cmd_pull_skill}[args.cmd](env, args)
if __name__ == "__main__": if __name__ == "__main__":