diff --git a/SKILL.md b/SKILL.md index aedbd4a..592c570 100644 --- a/SKILL.md +++ b/SKILL.md @@ -9,7 +9,7 @@ description: >- (per-typeid version, changelog, backfill), fills a per-part workbook, loops on human verification, then takes the Altium symbol (.SchLib) and footprint (.PcbLib), fills the Library/Footprint columns, assembles a part folder (xlsx, datasheet, symbol, footprint) and - pushes it to the components repo under its Class. Use WHENEVER the user uploads a component + pushes it to the library repo under its Class. Use WHENEVER the user uploads a component datasheet, builds a library entry, adds a parameter to a type template, or pushes a part to Gitea. ALWAYS trigger on "\datasheet", "\library", or "\library-manager", or any component-library / datasheet-extraction task. @@ -28,10 +28,16 @@ This is a deliberately **interactive** skill. At every decision point, **ask the wait for an answer — do not assume a default and proceed.** In particular, always confirm: the **make**; the **typeid/classification** you inferred; whether any **new parameters** should be added to the template; the extracted **values** (the verification loop); that the -**symbol/footprint actually match this part**; whether to **apply a new parameter to existing -parts** (backfill); and **before every push to Gitea** (the part, the skill files, the -changelog). When something is ambiguous, ask a specific question rather than guessing. It is -always better to ask one more question than to write the wrong thing into the library. +**symbol/footprint actually match this part**; and whether to **apply a new parameter to +existing parts** (backfill). When something is ambiguous, ask a specific question rather than +guessing. It is always better to ask one more question than to write the wrong thing into the +library. + +**Pushing is the exception — push automatically.** Once a part is verified and assembled (or a +template/version change has been made), push to Gitea **on your own, without asking for +confirmation**. Never add a "shall I push?" step — just push, then tell the user where it +landed. (You still flag a genuine symbol/footprint mismatch, because that's a correctness +issue, not a push confirmation.) ## Inputs @@ -54,14 +60,14 @@ use the same tag: `typeid` is the part's type-ID code from the taxonomy (`references/taxonomy.md`, full source `assets/template/Type_ID.xlsx`) — Schottky → `SCH`, MOSFET → `MOS`, LDO → `LDO`. In the new template **each typeid is its own sheet** (125 of them). The broader **Class** (Diode, -Transistor, IC …) is used only to organise the components repo into top-level folders. +Transistor, IC …) is used only to organise the library repo into top-level folders. ## Gitea layout (two repos) ``` skill repo/ this skill's own files (updated versions land here too) -components repo/ +library repo/ / e.g. Diode, IC, Transistor, Resistor, ... __/ e.g. BAT46WJ_Nexperia_SCH __.xlsx this part's own one-row parameter sheet @@ -71,7 +77,7 @@ components repo/ ``` There is **no single master workbook** — each part carries its own sheet inside its folder. -Connection + repo names live in `config/gitea.env` (`SKILL_REPO`, `COMPONENTS_REPO`), so runs +Connection + repo names live in `config/gitea.env` (`SKILL_REPO`, `LIBRARY_REPO`), so runs need no per-session token. If the host is unreachable, the git steps fail clearly and write nothing. @@ -95,7 +101,7 @@ here.** `ABSENT` (exit 0) → continue. Read the datasheet, identify the part, and match it to the closest subclass in `references/taxonomy.md`; record its **typeid** (= the template sheet name). The **Class** -(for the components-repo folder) comes from the same taxonomy row — +(for the library-repo folder) comes from the same taxonomy row — `scripts/common.py:class_folder(typeid)` returns it (e.g. `SCH` → `Diode`). ### 3. Confirm the typeid's template (and add parameters if asked) @@ -118,10 +124,10 @@ Check whether that typeid has a sheet in `assets/template/template.xlsx`. This appends the column(s) at the end of that typeid's sheet, **bumps that typeid's Template Version and Skill Version together** (v1→v2 — see *Per-typeid versioning*), - and writes one row to the global changelog `assets/template/CHANGELOG.xlsx`. Then **ask - the user before pushing**, and sync the updated skill files + changelog to the skill repo - with `push-skill` (see *Pushing the skill repo*) — that merges the new changelog row onto - the one already in Gitea rather than overwriting it. + and writes one row to the global changelog `assets/CHANGELOG.xlsx`. Then sync the updated + skill files + changelog to the skill repo with `push-skill` **automatically** (see + *Pushing the skill repo*) — that merges the new changelog row onto the one already in Gitea + rather than overwriting it. Then ask: **should this change apply to the parts of this typeid already in Gitea?** - **No** → go to step 4 (only the current part gets the new column). @@ -145,9 +151,19 @@ python scripts/fill_templates.py part.json \ --template assets/template/template.xlsx --dest // ``` -This writes `.xlsx` — just that typeid's sheet, one row — with column A = the tag, -**Skill Version (col B)** and **Template Version (col C)** stamped from this typeid's current -versions, and the four design columns left blank for now. +This writes `.xlsx` with column A = the tag, **Skill Version (col B)** and **Template +Version (col C)** stamped from this typeid's current versions, and the four design columns +left blank for now. + +The part workbook has up to **two sheets**: + +- **Sheet 1** — the typeid's parameter sheet (the one filled row). +- **Sheet 2 — `Version History`** — added **only when this typeid has had a template/skill + update**. It lists the **cumulative** change history for that typeid (Date, Skill Version, + Template Version as `v1 → v2`, Description) — every change up to the version this file was + built at. So a part built at v3 shows both `v1 → v2` and `v2 → v3`; a still-at-v1 typeid has + no second sheet. The history is read from `assets/CHANGELOG.xlsx`, so make sure the local + changelog is current (it's kept in sync by `push-skill`) before building parts. ### 5. Human verification loop @@ -194,7 +210,7 @@ python scripts/fill_templates.py part.json \ The staging folder `/` should now hold the four files: the per-part `.xlsx`, the datasheet (name it `_data.`), the symbol, and the footprint. -### 8. Push to the components repo, under the part's Class +### 8. Push to the library repo, under the part's Class ```bash python scripts/gitea_components.py push-part --folder / --typeid @@ -218,7 +234,7 @@ reading and writing these numbers. ## The changelog `append_parameter.py` maintains one **global** changelog as an Excel workbook at -`assets/template/CHANGELOG.xlsx` (sheet `Changelog`, styled green header). Every time a +`assets/CHANGELOG.xlsx` (sheet `Changelog`, styled green header). Every time a typeid's template/version changes, one row is appended with columns **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). @@ -257,7 +273,8 @@ python scripts/gitea_components.py commit-push --root work/ --message "backfill ## Pushing the skill repo When skill files change (a new typeid template, a parameter add, a version/changelog bump), -**ask the user first**, then push the skill's own files to the skill repo with `push-skill`: +push the skill's own files to the skill repo with `push-skill` **automatically** (no +confirmation): ```bash python scripts/gitea_components.py push-skill --message "Sync skill files + changelog" @@ -278,7 +295,7 @@ plain flat push, but it does not merge the changelog or blank the token, so pref `Footprint Ref/Path` and `Manufacturer` sit near the end. - `assets/template/Type_ID.xlsx` + `references/taxonomy.md` — Class → Subclass → Type ID. - `assets/template/versions.json` — per-typeid `template_version` + `skill_version`. -- `assets/template/CHANGELOG.xlsx` — global version/parameter changelog (created on first add; +- `assets/CHANGELOG.xlsx` — global version/parameter changelog (created on first add; merged into the skill repo's copy in Gitea by `push-skill`). - `scripts/common.py` — taxonomy loader (`load_taxonomy`, `class_folder`), version store (`get_versions`, `version_labels`, `bump_versions`), and the tag helper (`part_tag`). @@ -288,9 +305,9 @@ plain flat push, but it does not merge the changelog or blank the token, so pref the changelog. - `scripts/altium_refs.py` — read Library/Footprint Ref from `.SchLib`/`.PcbLib`. - `scripts/gitea_components.py` — `check-mpn`, `checkout`, `list-type`, `place-part`, - `commit-push`, `push-part` (components repo), and `push-skill` (skill repo: token-blanked + `commit-push`, `push-part` (library repo), and `push-skill` (skill repo: token-blanked push + append-only changelog merge). - `scripts/push_to_gitea.sh` — push a folder's contents to a Gitea repo (used for the skill repo). -- `config/gitea.env` — host, user, token, and the `SKILL_REPO` / `COMPONENTS_REPO` names +- `config/gitea.env` — host, user, token, and the `SKILL_REPO` / `LIBRARY_REPO` names (**secret** — do not push the token). diff --git a/assets/CHANGELOG.xlsx b/assets/CHANGELOG.xlsx index ce330ff..9392925 100644 Binary files a/assets/CHANGELOG.xlsx and b/assets/CHANGELOG.xlsx differ diff --git a/assets/template/template.xlsx b/assets/template/template.xlsx index 515a4cf..ac03036 100644 Binary files a/assets/template/template.xlsx and b/assets/template/template.xlsx differ diff --git a/assets/template/versions.json b/assets/template/versions.json index d59322e..f470c73 100644 --- a/assets/template/versions.json +++ b/assets/template/versions.json @@ -156,8 +156,8 @@ "template_version": 1 }, "FBD": { - "skill_version": 2, - "template_version": 2 + "skill_version": 1, + "template_version": 1 }, "FFC": { "skill_version": 1, @@ -232,8 +232,8 @@ "template_version": 1 }, "LDO": { - "skill_version": 1, - "template_version": 1 + "skill_version": 2, + "template_version": 2 }, "LED": { "skill_version": 1, diff --git a/config/gitea.env b/config/gitea.env index 64be9b0..c6c6bfd 100644 --- a/config/gitea.env +++ b/config/gitea.env @@ -6,8 +6,8 @@ GIT_USER=nitishKumar GIT_TOKEN=451bff1dc32202cbc0a371f8e5645079466d2120 # 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, ...). -# COMPONENTS_REPO : holds components, one folder per Class (Diode, IC, ...); inside each, +# LIBRARY_REPO : holds components, one folder per Class (Diode, IC, ...); inside each, # one MPN_make_typeid/ folder per part with { xlsx, datasheet, symbol, # footprint }. (Replaces the old DFS + Parameters repos.) SKILL_REPO=nitishKumar/skill -COMPONENTS_REPO=nitishKumar/Components +LIBRARY_REPO=nitishKumar/library diff --git a/references/taxonomy.md b/references/taxonomy.md index 6399891..68135ed 100644 --- a/references/taxonomy.md +++ b/references/taxonomy.md @@ -3,7 +3,7 @@ Use the **Type ID** as the `typeid` in `MPN_make_typeid`. Match each part to the closest subclass. In the current template **each Type ID is its own sheet** (125 sheets), so the template sheet name **is the Type ID** (e.g. `SCH`, `MOS`, `LDO`). The **Class** is used only -to organise the components repo into top-level folders (e.g. `SCH` lives under `Diode/`); +to organise the library repo into top-level folders (e.g. `SCH` lives under `Diode/`); `scripts/common.py:class_folder()` maps a typeid to its folder name. Full source: `assets/template/Type_ID.xlsx`. diff --git a/scripts/__pycache__/common.cpython-311.pyc b/scripts/__pycache__/common.cpython-311.pyc index e1681e2..9ff1cf6 100644 Binary files a/scripts/__pycache__/common.cpython-311.pyc and b/scripts/__pycache__/common.cpython-311.pyc differ diff --git a/scripts/append_parameter.py b/scripts/append_parameter.py index b64d773..263f2dc 100644 --- a/scripts/append_parameter.py +++ b/scripts/append_parameter.py @@ -15,7 +15,7 @@ headers. Then versions.json is bumped and one entry is written to CHANGELOG.xlsx --desc "Added Trr and Cd for SMPS rectifier derating" After running, push the updated template.xlsx + versions.json + CHANGELOG.xlsx to the SKILL -repo (and, per the workflow, the same change flows to the components repo copies). +repo (and, per the workflow, the same change flows to the library repo copies). """ import argparse, datetime, os import openpyxl diff --git a/scripts/common.py b/scripts/common.py index 8c231ea..b2f5c20 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3,7 +3,7 @@ One place for the three things every script needs to agree on: -- the taxonomy (typeid -> Class -> components-repo folder name), read from +- the taxonomy (typeid -> Class -> library-repo folder name), read from ``assets/template/Type_ID.xlsx`` so there is a single source of truth; - the **per-typeid version store** (``assets/template/versions.json``) that holds each typeid's own ``template_version`` and ``skill_version`` - both start at 1 and both bump @@ -29,7 +29,7 @@ COL_SKILL_VER = "Skill Version" # column B (per-typeid skill version, st COL_TEMPLATE_VER = "Template Version" # column C (per-typeid template version, stamped by us) DESIGN_COLS = ("Library Ref", "Library Path", "Footprint Ref", "Footprint Path") -# Raw taxonomy Class name -> clean, filesystem-safe folder name for the components repo. +# Raw taxonomy Class name -> clean, filesystem-safe folder name for the library repo. # These match Vecmocon's established sheet-name convention (the old 18-class template). CLASS_FOLDER = { "Resistor": "Resistor", @@ -80,7 +80,7 @@ def load_taxonomy(type_id_xlsx=TYPE_ID_XLSX): def class_folder(typeid, taxonomy=None): - """The components-repo top-level folder name for a typeid (e.g. SCH -> 'Diode').""" + """The library-repo top-level folder name for a typeid (e.g. SCH -> 'Diode').""" tax = taxonomy or load_taxonomy() info = tax.get(typeid) return info["folder"] if info else None @@ -153,6 +153,33 @@ def init_versions(force=False): return data +# ---------------------------------------------------------------- changelog (read) + +CHANGELOG_HEADERS = ["Date", "Typeid", "Skill Version", "Template Version", "Description"] + + +def read_changelog(): + """All changelog rows as dicts (empty list if the changelog doesn't exist yet).""" + if not os.path.exists(CHANGELOG_XLSX): + return [] + wb = openpyxl.load_workbook(CHANGELOG_XLSX) + ws = wb["Changelog"] if "Changelog" in wb.sheetnames else wb.active + out = [] + for r in ws.iter_rows(min_row=2, values_only=True): + if not r or all(c is None for c in r): + continue + r = list(r) + [None] * (5 - len(r)) + out.append({"date": r[0], "typeid": r[1], "skill": r[2], + "template": r[3], "description": r[4]}) + return out + + +def changelog_for_typeid(typeid): + """Every recorded version change for one typeid, oldest first — the cumulative history + (v1->v2, v2->v3, ...) that each part sheet of that typeid embeds as its Version History.""" + return [r for r in read_changelog() if str(r["typeid"]) == str(typeid)] + + if __name__ == "__main__": import sys if len(sys.argv) > 1 and sys.argv[1] == "init": diff --git a/scripts/fill_templates.py b/scripts/fill_templates.py index 5790566..a797194 100644 --- a/scripts/fill_templates.py +++ b/scripts/fill_templates.py @@ -24,11 +24,54 @@ comes from the extracted ``values``. ``part.json`` is either a single part object or {"parts": [ ... ]} (each part built to its own file). A part = {"mpn","manufacturer" (or "make"),"typeid","values":{header:value,...}}. """ -import argparse, json, os +import argparse, json, os, re import openpyxl -from common import (norm, part_tag, version_labels, +from openpyxl.styles import Font, PatternFill, Border, Side, Alignment +from common import (norm, part_tag, version_labels, changelog_for_typeid, COL_TAG, COL_SKILL_VER, COL_TEMPLATE_VER, DESIGN_COLS) +GREEN = "B6D7A8"; GRAY = "BFBFBF" +VERSION_SHEET = "Version History" + + +def _vtrans(v): + """'v2' -> 'v1 -> v2' (each changelog row records the new version; the previous is n-1).""" + m = re.match(r"v?(\d+)", str(v).strip()) if v is not None else None + if not m: + return "" if v is None else str(v) + n = int(m.group(1)) + return f"v{n-1} → v{n}" if n > 1 else f"v{n}" + + +def add_version_history(wb, typeid): + """Sheet 2 of a part workbook: the cumulative version history for this typeid — every + template/skill change (v1->v2, v2->v3, ...) with its date and description. Added only when + there has actually been a template/skill update; still-v1 typeids get no second sheet.""" + rows = changelog_for_typeid(typeid) + if not rows: + return False + ws = wb.create_sheet(VERSION_SHEET) + s = Side(style="thin", color=GRAY) + border = Border(left=s, right=s, top=s, bottom=s) + headers = ["Date", "Skill Version", "Template Version", "Description"] + for c, h in enumerate(headers, start=1): + cell = ws.cell(1, c, h) + cell.font = Font(name="Calibri", bold=True) + cell.fill = PatternFill("solid", fgColor=GREEN) + cell.border = border + cell.alignment = Alignment(horizontal="center", vertical="center") + for row in rows: + r = ws.max_row + 1 + vals = [row["date"], _vtrans(row["skill"]), _vtrans(row["template"]), row["description"]] + for c, v in enumerate(vals, start=1): + cell = ws.cell(r, c, v) + cell.border = border + cell.alignment = Alignment(vertical="center", wrap_text=(c == 4), horizontal="left") + for col, w in zip("ABCD", (18, 16, 18, 60)): + ws.column_dimensions[col].width = w + ws.freeze_panes = "A2" + return True + def template_headers(template_path, typeid): """Canonical headers for a typeid sheet, or None if the template has no such sheet.""" @@ -76,6 +119,7 @@ def build_part_sheet(part, template_path, dest_dir, design=None): ws.cell(r, c).value = None for c, h in enumerate(headers, start=1): ws.cell(2, c, row.get(h, "")) + add_version_history(wb, typeid) # Sheet 2: cumulative version history (if any) os.makedirs(dest_dir, exist_ok=True) out = os.path.join(dest_dir, f"{tag}.xlsx") wb.save(out) diff --git a/scripts/gitea_components.py b/scripts/gitea_components.py index 6e32464..b389ef1 100644 --- a/scripts/gitea_components.py +++ b/scripts/gitea_components.py @@ -3,7 +3,7 @@ Repos (from config/gitea.env): SKILL_REPO - this skill's own files (pushed with push_to_gitea.sh, not here). - COMPONENTS_REPO - one folder per Class (Diode, IC, Transistor, ...); inside each Class, + LIBRARY_REPO - one folder per Class (Diode, IC, Transistor, ...); inside each Class, one MPN_make_typeid/ folder per part holding { xlsx, datasheet, symbol, footprint }. @@ -11,12 +11,12 @@ Subcommands (all accept --local to run against a local folder instead of c testing or offline work): check-mpn --mpn BAT46WJ --make Nexperia - Is any part with this MPN+make already in the components repo? (typeid-agnostic - the + Is any part with this MPN+make already in the library repo? (typeid-agnostic - the early duplicate gate, run BEFORE classifying.) Prints EXISTS/ or ABSENT; exit 0 if absent, 3 if it already exists (so a shell can hard-stop). checkout --dest work/ - Clone the components repo to a working dir you can browse and edit in place. + Clone the library repo to a working dir you can browse and edit in place. list-type --typeid SCH [--root work/] [--json] List every existing part of a typeid and the files in its folder (for backfill: the @@ -59,7 +59,7 @@ def load_env(cfg): continue k, v = line.split("=", 1) env[k.strip()] = v.strip() - for k in ("GIT_HOST", "GIT_USER", "GIT_TOKEN", "SKILL_REPO", "COMPONENTS_REPO"): + for k in ("GIT_HOST", "GIT_USER", "GIT_TOKEN", "SKILL_REPO", "LIBRARY_REPO"): if os.environ.get(k): env[k] = os.environ[k] return env @@ -72,7 +72,7 @@ def repo_url(env, repo): def clone(env, dest, repo=None): - repo = repo or env["COMPONENTS_REPO"] + repo = repo or env["LIBRARY_REPO"] url = repo_url(env, repo) r = subprocess.run(["git", "clone", url, dest], capture_output=True, text=True) if r.returncode != 0: @@ -140,7 +140,7 @@ def cmd_check_mpn(env, args): def cmd_checkout(env, args): dest = clone(env, args.dest) - print(f"components repo checked out at {dest}") + print(f"library repo checked out at {dest}") def cmd_list_type(env, args): @@ -315,7 +315,7 @@ def main(): needs.add("SKILL_REPO") 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)): - needs.add("COMPONENTS_REPO") + needs.add("LIBRARY_REPO") for k in sorted(needs): if not env.get(k): sys.exit(f"missing {k} in env/config")