Files
shopdb-flask/.github/copilot-instructions.md
benjamin.baker f02e344101 Correct two gate commands that pass without running
npx vitest run bypasses the pretest hook that stages plugin frontends, so
plugin specs were never collected - it reported 28 spec files where npm test
reports 30. A plugin frontend spec could pass CI unread. Bare bash resolves to
WSL on Windows dev boxes and fails to find /bin/bash, which redirected to a log
reads as a clean style run with no findings.

Both are the same failure shape: a command that reports success without having
done the work.

Also records that a multi-line here-string passed to git commit -m silently
no-ops, reporting success without creating the commit.
2026-09-09 06:37:26 -04:00

7.3 KiB

, # Copilot instructions for shopdb-flask

Follow these when suggesting code. They are enforced by a naming/style hook and by CI (.github/workflows/ci.yml) - suggestions that break them fail the build. CONTRIBUTING.md is the full authority; this is the short version.

General Guidelines

  • Finish the in-flight task before starting new workstreams.
  • When asked to "take notes" on a future idea, capture it in the notes file only - do not implement it, and do not expand on it at length in replies.
  • Use a temp file for multi-line git commit messages instead of heredoc syntax: git commit -F <file>. When committing in PowerShell, always write the message to a temp file in the WORKSPACE ROOT (e.g. commitmsg.tmp), run git commit -F commitmsg.tmp, then delete the file. Passing a multi-line here-string to git commit -m silently no-ops: it reports success but does not create the commit. Always verify with git log after committing. Do NOT place the temp file inside .git/ - git prunes stray files there and the file disappears before commit runs.
  • Avoid using powershell.exe -File for launching scripts, as it fails if the script file is missing or quarantined.
  • Long-running commands (pytest suites, npm builds, container rolls) must be redirected to a log file under devnotes/ (e.g. devnotes/_pytest.log) and launched non-blocking; never run as a blocking foreground command. Do not poll or tail the output; the user watches the file and will say when it is done.
  • Before taking any action that modifies code or files, warn the user first if a test run or build may be in progress, so discussion about the project can continue without altering files underneath a running test.
  • Always invoke shell scripts with the explicit Git Bash path: & "C:\Program Files\Git\bin\bash.exe" scripts/<script>.sh to avoid issues with WSL resolving bare bash in PowerShell.
  • Do not present findings as narrow multiple-choice questions when the user is catching up on context; lay out the decision and its evidence in prose instead.

Installer Logic

  • The endpoint agent installer shipped to endpoints is generated by _install_script() in plugins/endpoints/api/siteagent.py, NOT agent/installer/install-shopdbagent.ps1. Any installer logic change must be made in siteagent.py, followed by rolling the containers.
  • The user's workflow: the agent hands over only the built EXEs (shopdbagent.exe, shopdb-endpoint-tray.exe); the user uploads them via the "Upload New/Legacy Agents" field in the Build Site Agent UI and builds the package with the newest version selected at the top.

Naming (LOCKED - the hook rejects violations)

  • DB tables: lowercase, concatenated, plural. No underscores, no dashes. machines, networkdevices, businessunits - NOT machine_types, BusinessUnits.
  • DB columns: lowercase, concatenated, singular. No underscores. machineid, lastzabbixsync, isactive - NOT machine_id, last_zabbix_sync.
  • Foreign keys: referenced table (singular) + id: locationid, vendorid.
  • Booleans: is/has prefix: isactive, isshopfloor.
  • Index names: idx_<table>_<column> (underscores allowed here only).

Python

  • A variable, attribute, function, or dict key that holds a DB value MUST match the column name exactly - do NOT convert to snake_case. Column machineid -> Machine.machineid, {"machineid": 1}, local machineid.
  • Pure code that does NOT mirror a DB field uses normal snake_case (PEP 8): loop_count, current_user, validate_input().
  • Classes: PascalCase, spelled out (NetworkDevice, AssetType).

JavaScript / Vue

  • A JS variable holding an API field value matches the API key exactly - do NOT camelCase it. API {"machineid": 1} -> response.machineid, never machineId.
  • Components: PascalCase (AssetDetail.vue). CSS classes: lowercase-with-dashes.

API

  • Endpoints: lowercase plural nouns, no underscores/dashes: /api/networkdevices.
  • Query params + response keys match column names: ?locationid=5, {"machineid": 1, "lastzabbixsync": "..."}.

Allowed acronyms only

Universal: id url api http https json jwt sql os ip dns csv pdf cors ttl uuid html css orm. Domain: cmm cnc pc usb vnc winrm ssh ssl tls tcp udp smtp ldap vlan sso dnc focas clm mtconnect. Anything else: spell it out.

Banned shorthand

Never use cfg ctx mgr req res env util helper or db as a standalone variable name. Spell out: config context manager request response environment utilities. _bp is fine only as a suffix with a meaningful prefix (printers_bp).

Style (ASCII only)

  • NO emojis anywhere - code, comments, strings, UI.
  • NO em-dashes, en-dashes, Unicode arrows, or smart quotes. Plain ASCII only.
  • Comments default to NONE. Add one only when the WHY is non-obvious; keep inline #/// comments terse. Docstrings stay normal English.
  • Dark theme is the default; keep UI functional and professional.

Architecture (do not violate)

  • Plugins are the product. Plugin code lives in plugins/<name>/{models,api,services,schemas}/ with a manifest.json (single source of truth: name, version, dependencies, api_prefix) and a BasePlugin subclass in plugin.py.
  • Plugins never import core internals. Use the contract surface shopdb.api (e.g. from shopdb.api import db, Asset, success_response). Adding to that surface is a contract-version bump + a docs/PLUGIN-HOOKS update in the same PR.
  • Migrations, never db.create_all(). The core Alembic chain is in migrations/versions/; each plugin owns its own chain under plugins/<name>/migrations/. New schema = a new migration with an idempotent guard and a real downgrade. Migrations must run clean on strict MySQL 8. Any model column/table change ships WITH its migration in the SAME change, and bringing up a new image MUST run flask db upgrade then flask plugin upgrade-all before serving traffic. A model that references a column its migration has not applied yet 500s every query on that table (1054 Unknown column), so code and schema must never be deployed out of step.
  • Asset model is the platform contract. Physical things are an Asset plus a plugin subtype row linked by assetid (FK, ON DELETE CASCADE). Consumables with quantities are standalone tables, not assets.
  • Ledger pattern: a cached quantityonhand moves in the SAME commit as the signed transaction row it reflects.
  • Configuration for endpoint agent: The endpoint agent's live collector key and base URL are provisioned in the Windows registry HKLM\SOFTWARE\GE\ShopDB (values CollectorKey, BaseUrl), not committed in appsettings.json; appsettings.json holds only non-secret runtime settings.

Before you finish a change

Run the three gates (CI runs the same):

python -m pytest tests/ -q
cd frontend && npm test && npm run build && cd ..
& "C:\Program Files\Git\bin\bash.exe" scripts/check-naming-and-style.sh

npm test, NOT npx vitest run: the pretest hook runs stage-frontend.mjs, so bare vitest never stages plugin frontends and silently skips their specs. Call the style script through the explicit Git Bash path - bare bash resolves to WSL on Windows dev boxes, fails to find /bin/bash, and reads as a clean run with no findings.

Commits: short present-tense subject, body says WHY. No AI/tool attribution in commit messages, code comments, or docs.