diff --git a/.cursor/rules/update-master-spec-before-push.mdc b/.cursor/rules/update-master-spec-before-push.mdc new file mode 100644 index 0000000..26f6da0 --- /dev/null +++ b/.cursor/rules/update-master-spec-before-push.mdc @@ -0,0 +1,20 @@ +--- +description: Refresh GISHEN-MASTER-SPEC (and admin-backend-spec) before git push +alwaysApply: true +--- + +# Update master spec before push + +Before any **`git push`** (or when the user asks to push / open a PR that pushes): + +1. Update [`docs/GISHEN-MASTER-SPEC.md`](docs/GISHEN-MASTER-SPEC.md): + - Append a **Changelog** row (today’s date + why). + - Refresh sections touched by this branch (auth, orgs, doctors, orders/guest, Rx dosing, catalogue/UOM/PDP, stock, loyalty). + - Re-skim sibling folders under `/Users/kirukib/Desktop/Yaltopia Project/` when present: `Gishen-Mob`, `Gishen-Ecom`, `Gishen-B2B` (and Dispatch Bot if dispatch changed). Note missing folders under **Sources not found**. + - Bump **Last reconciled** + Sources stamp checklist dates. +2. If Admin modules, mocks, or endpoints changed, also update [`docs/admin-backend-spec.md`](docs/admin-backend-spec.md) changelog + relevant sections. +3. Do **not** invent APIs — mark **TBD** when siblings lack a contract. +4. Prefer running `bash scripts/sync-master-spec-reminder.sh` and including refreshed docs in the commit set before push. +5. Never skip this for “docs-only is fine later” when `src/` or backend sheets changed. + +Do not push unless the user explicitly asked to push. diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..d89b19a --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Optional pre-push: warn if GISHEN-MASTER-SPEC may be stale. +# Soft-fail by default. Set GISHEN_MASTER_SPEC_STRICT=1 to block push. +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +SCRIPT="$ROOT/scripts/sync-master-spec-reminder.sh" + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " Gishen: master spec check (pre-push)" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +if [[ ! -x "$SCRIPT" && -f "$SCRIPT" ]]; then + chmod +x "$SCRIPT" || true +fi + +if [[ -f "$SCRIPT" ]]; then + # Soft warn unless STRICT already set by user + bash "$SCRIPT" || { + status=$? + if [[ "${GISHEN_MASTER_SPEC_STRICT:-0}" == "1" ]]; then + echo "Push blocked: refresh docs/GISHEN-MASTER-SPEC.md then retry." + exit "$status" + fi + echo "Continuing push (soft warn). Re-run with GISHEN_MASTER_SPEC_STRICT=1 to enforce." + } +else + echo "WARN: missing $SCRIPT — update docs/GISHEN-MASTER-SPEC.md before push." +fi + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +exit 0 diff --git a/README.md b/README.md index 48c7275..9d79c5b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,15 @@ Demo password for all accounts: `demo123` ## Docs -Living backend contract: [`docs/admin-backend-spec.md`](docs/admin-backend-spec.md) — updated whenever modules are finalized. +- **Cross-app master:** [`docs/GISHEN-MASTER-SPEC.md`](docs/GISHEN-MASTER-SPEC.md) — Admin / Mob / Ecom / B2B surface map, shared domain objects, auth providers, divergence notes. **Refresh before `git push`** (Cursor rule + optional `.githooks/pre-push`). +- **Admin backend contract:** [`docs/admin-backend-spec.md`](docs/admin-backend-spec.md) — updated whenever Admin modules are finalized. + +```bash +# One-time: enable in-repo pre-push reminder for this clone +bash scripts/setup-githooks.sh +# Manual checklist / staleness check +bash scripts/sync-master-spec-reminder.sh +``` ## Deploy @@ -56,5 +64,9 @@ VITE_API_BASE_URL= ## Related repos +Sibling apps under `Yaltopia Project/` (see master spec Sources stamp): + +- Gishen-Mob — customer mobile (Expo) - Gishen-Ecom — customer storefront - Gishen-B2B — institutional portal (org self-register UI lives there; Admin creates/approves orgs) +- Gishen-Dispatch-Bot — rider Telegram bot diff --git a/docs/GISHEN-MASTER-SPEC.md b/docs/GISHEN-MASTER-SPEC.md new file mode 100644 index 0000000..042bc2f --- /dev/null +++ b/docs/GISHEN-MASTER-SPEC.md @@ -0,0 +1,313 @@ +# Gishen — Cross-App Master Spec + +**Living collective contract** across Gishen client apps. Deeper API sketches live in each repo’s own backend sheet; this file is the **shared map** of roles, domain objects, auth providers, and known divergences. + +| Field | Value | +| --- | --- | +| **Home repo (this file)** | `Gishen-Admin` | +| **Sibling root** | `/Users/kirukib/Desktop/Yaltopia Project/` | +| **Shared API base (TBD)** | `https://api.gishen.example/v1` (Ecom/Mob/Admin) · B2B draft: `https://api.gishenpharmacy.org/v1` — **unify TBD** | +| **Last reconciled** | 2026-08-07 | + +--- + +## Changelog + +| Date | Change | +| --- | --- | +| 2026-08-07 | **Seed batch:** Created master spec from Admin living contract + sibling Mob/Ecom/B2B/Dispatch sheets. Documented product surface map, shared domain objects (auth, orgs, doctors, orders/guest, Rx dosing, catalogue multi-UOM + PDP, stock, loyalty), auth provider matrix, divergence & sync notes, deeper-doc links, Sources stamp, and pre-push update rule. Wired `.cursor/rules` + optional `.githooks/pre-push` reminder. Linked from Admin `README.md` and `admin-backend-spec.md`. | + +--- + +## Update rule (before every push) + +This file is the **cross-app master**. Before `git push` from Gishen-Admin (humans **and** agents): + +1. Diff Admin `src/` / `docs/admin-backend-spec.md` against the last Sources stamp below. +2. Skim sibling Mob / Ecom / B2B / Dispatch specs if those folders exist on disk. +3. Refresh relevant sections here; append a **Changelog** row (date + why). +4. If Admin endpoints/fields changed, also update [`admin-backend-spec.md`](./admin-backend-spec.md) in the same change set. +5. Update the **Sources stamp** (`Last reconciled` + checklist dates). + +Agents: the Cursor rule `.cursor/rules/update-master-spec-before-push.mdc` applies on push requests. Optional git hook: see [Pre-push tooling](#pre-push-tooling). + +--- + +## Sources stamp + +Update these dates whenever you reconcile against a sibling. + +| Source | Path (sibling area) | Found | Spec / README | Last skimmed | +| --- | --- | --- | --- | --- | +| **Admin** | `Gishen-Admin/` | ✅ | [`docs/admin-backend-spec.md`](./admin-backend-spec.md) | 2026-08-07 | +| **Mob** | `Gishen-Mob/` | ✅ | [`docs/BACKEND_SPEC.md`](../../Gishen-Mob/docs/BACKEND_SPEC.md) (relative from sibling root) | 2026-08-07 | +| **Ecom** | `Gishen-Ecom/` | ✅ | [`docs/backend.md`](../../Gishen-Ecom/docs/backend.md) | 2026-08-07 | +| **B2B** | `Gishen-B2B/` | ✅ | [`docs/backend/OVERVIEW.md`](../../Gishen-B2B/docs/backend/OVERVIEW.md) + coordination | 2026-08-07 | +| **Dispatch Bot** | `Gishen-Dispatch-Bot/` | ✅ (related) | `docs/BACKEND_SPEC.md` | 2026-08-07 | +| **Telegram Mini App** | `Gishen-Telegram-MiniApp/` | ⚠️ empty scaffold | — | 2026-08-07 | + +### Sources not found + +_None for the four primary surfaces (Admin / Mob / Ecom / B2B)._ Related folders present but thin: `Gishen-Telegram-MiniApp` (empty), Dispatch Bot documented as rider surface. + +### Pre-push checklist (human / agent) + +- [ ] Changelog row added if domain contract changed +- [ ] Auth / orders / Rx / catalogue / org notes still match siblings (or divergences listed) +- [ ] `Last reconciled` date bumped +- [ ] `admin-backend-spec.md` updated if Admin modules changed +- [ ] Reminder script run (optional): `bash scripts/sync-master-spec-reminder.sh` + +--- + +## 1. Product surface map + +One shared pharmacy platform; **four primary clients** + rider bot. + +| App | Repo | Who | Primary jobs | +| --- | --- | --- | --- | +| **Admin** | `Gishen-Admin` | Pharmacy staff: `pharmacist`, `stock_manager`, `procurement`, `finance`, `marketing_manager`, `operations`, `super_admin` | Branch Rx queue, orders/POS, stock, procurement, org commercial activation, CRM/loyalty config, dispatch board, team/audit, catalogue master, doctor roster | +| **Mob** | `Gishen-Mob` | Retail customer (Expo) | Catalog/PDP, cart/checkout, guest pharmacist codes, Rx Care (scan/reminders), loyalty, family, subscriptions, B2B join/contexts, tracking | +| **Ecom** | `Gishen-Ecom` | Retail customer (Next.js PWA) | Storefront catalog, Rx upload/check, cart → order request, profile/favorites/loyalty, branches; v1 confirmation often phone/WhatsApp | +| **B2B** | `Gishen-B2B` | Institutional: `SUPER_USER`, `HR_ADMIN`, `FINANCE`, `MEMBER` | Org self-register; HR members/packages/migration; Finance statements/approvals; Member allowance + own Rx (**clinical withhold** for HR/Finance) | +| **Dispatch Bot** | `Gishen-Dispatch-Bot` | Riders/drivers | Telegram-enrolled trip status + POD; no separate rider login app for v1 | +| **Telegram Mini App** | `Gishen-Telegram-MiniApp` | Customers (planned) | Placeholder — Ecom backend mentions Telegram auth/bootstrap; **TBD** | + +### Who creates what + +| Artefact | Created by | Verified / fulfilled by | +| --- | --- | --- | +| Retail order (registered) | Customer (Mob/Ecom) | Pharmacist / Ops (Admin) | +| Guest order / pharmacist code (`GSH-XXXX`) | Guest/customer Mob/Ecom; staff/doctor Admin | Pharmacist Admin (`by-code` / POS) | +| Prescription intake (customer scan) | Mob/Ecom/B2B member | Pharmacist Admin | +| Doctor-authored Rx / clinical order | Doctor (hospital org) | Pharmacist Admin | +| Catalogue Item master | Stock/super_admin Admin | Consumed by Mob/Ecom as `/catalog/products` | +| Org commercial terms | Admin Finance | B2B unlocks on `org.activated` | +| Trip assign | Ops Admin | Dispatch Bot rider | + +### External actors (not Admin staff roles) + +| Actor | Auth surface | Notes | +| --- | --- | --- | +| **Doctor** | Separate doctor JWT (Admin sheet); hospital/clinic org affiliation | Create Rx & guest/registered patient orders; **not** a `StaffRole` | +| **End customer (guest)** | None / Mob `POST /auth/guest` / Ecom guest order | Phone required for assisted/guest checkout; loyalty gated | +| **B2B roles** | B2B portal session | Distinct from pharmacy staff; shared `customer_id` linkage TBD | + +--- + +## 2. Shared domain objects + +Fields below are the **platform-level** vocabulary. Path prefixes and exact DTOs still differ per sheet — see [§4 Divergence](#4-divergence--sync-notes). + +### 2.1 Users & auth providers + +| Concept | Shared idea | App notes | +| --- | --- | --- | +| Platform person | UUID user / customer | Staff vs customer vs doctor vs B2B user are **actors**, not one role enum everywhere | +| Staff | Admin `StaffUser` + `StaffRole` + optional `branchId` | Bearer JWT | +| Customer | Retail identity; loyalty points/tier | Mob `GET /users/me`; Ecom same idea | +| Doctor | Hospital/clinic-affiliated clinician | Admin roster + `POST /auth/doctor/login` | +| B2B session user | `roles[]`, `org_id`, `active_role` | Clinical withhold for HR/Finance | +| `authProvider` | `email` \| `google` \| `phone` \| `telegram` (+ Mob `guest`) | Report on `/me` / login response | +| Locale | `en` \| `am` | Admin `preferredLocale`; Mob/Ecom/B2B `locale` / Accept-Language | + +See [§3 Auth matrix](#3-auth-provider-matrix). + +### 2.2 Organisations + +| Type (Admin) | Used for | +| --- | --- | +| `corporate` \| `ngo` \| `other` | B2B benefits clients — credit, packages, members | +| `hospital` \| `clinic` | Doctor affiliation; clinical Rx/orders | + +**Lifecycle (B2B):** `pending_activation` → `active` → `suspended` \| `closed`. Admin writes commercial terms (`credit`, payment terms, `price_list_id`, contract dates) and emits activation events. B2B self-register UI → Admin approval queue. + +### 2.3 Doctors + +- Roster under hospital/clinic orgs in Admin (`/doctors`, org detail). +- Fields (Admin): `name`, `specialty`, `licenseNumber`, `phone`, `email`, `status`, `orgId`, … +- Can create prescriptions and orders for **guest** or registered patients, org-scoped. +- Cannot manage catalogue, RBAC, finance, other hospitals. +- Identity provider strategy vs B2B hospital users: **open** (Admin Q8 / Q17). + +### 2.4 Orders (registered + guest) + +| Field / idea | Meaning | +| --- | --- | +| `customerType` | Admin: `registered` \| `guest` on staff/doctor-created orders | +| Guest identity | `customerName` + **required** `customerPhone`; no `customerId` | +| Channels | Admin includes branch / POS / doctor / (ops); Mob/Ecom retail + guest codes | +| Pharmacist code | Mob/Ecom: `GSH-XXXX` via `POST /orders/guest-codes`; Admin fulfill `GET/POST .../by-code/:code` | +| Fulfillment | `pickup` \| `delivery` (+ branch / any-branch for guest codes) | +| Payments | Mob: Chapa / Telebirr / M-Pesa for delivery; `pay_at_branch` pickup-only. Ecom v1 often offline confirm | + +**Guest limits (proposed):** valid phone; controlled SKUs still need approved Rx; no loyalty earn until claim/merge — **TBD** claim-by-OTP (Admin Q9–10). + +### 2.5 Prescriptions + dosing schedule + +| Layer | Contract | +| --- | --- | +| Admin line item | `frequency?`: `QD`\|`BID`\|`TID`\|`QID`\|`QXH`\|`custom`; `intervalHours?` (QXH); `times?`: `HH:mm`[] | +| Mob reminders | `POST /health/reminders` with `times[]`, grace, escalate; calendar UI; confirm/snooze | +| Sync intent | Persist pharmacist final `times` (+ interval) on approve/dispense so Mob can seed reminders without re-derive — **when** to push still open (Admin Q13) | +| Customer Rx Care | Mob/Ecom upload + `prescriptions/check` stock match statuses | +| B2B | Member submits Rx; HR/Finance never see clinical lines | + +Admin helper: `src/lib/dosingSchedule.ts`. + +### 2.6 Catalog / Items + multi-UOM + storefront PDP + +| Concept | Admin (source of truth for ops) | Mob / Ecom (retail read model) | +| --- | --- | --- | +| Master product | `/admin/catalog/items` — Item with SKU, classification, media, EFDA, … | `/catalog/products` (+ slug) | +| Stock link | Inventory rows reference `itemId` | `/stock/product/:productId`, branches nearby | +| Multi-UOM | `uoms[]` + `conversionFactor` into stock `baseUnit`; `toStockQty` / `fromStockQty` | **Not yet** on Mob product type — sell unit is display `unit` string only | +| PDP fields | `storefrontUnit`, `useCase`, `compareAtPriceEtb`, side-effect lists, `directions[]`, `storageNotes[]`, `pharmacistTip` | Mob PDP: `product` + `getDrugFacts()` clinical overlay (`useCase`, form, dosages, mild/severe, storage, directions, advice) | +| Pricing lists | B2B `price_list_id` on org commercial | Ecom/Mob show retail (and credit quote at checkout) | + +### 2.7 Stock + +- Branch qty, batch/lot, expiry on **Stock**, not Item. +- ERP sync / field ownership / merge proposals: Admin Stock module. +- Mob/Ecom: availability APIs only (no warehouse write). + +### 2.8 Loyalty + +| Surface | Role | +| --- | --- | +| Admin Marketing | Config, ledger, activities, CRM points adjust, liability KPI; POS earn | +| Mob / Ecom | `GET /loyalty/me`, ledger, redeem; referrals (Ecom) | +| Guests | No earn by default until registered claim | +| Rx/Controlled | Catalogue auto-excludes loyalty eligibility (Admin) | +| B2B | Pointer/summary only; retail owns full wallet UI | + +--- + +## 3. Auth provider matrix + +| Provider | Admin (staff) | Mob (customer) | Ecom (customer) | B2B (portal) | Dispatch (rider) | Doctor | +| --- | --- | --- | --- | --- | --- | --- | +| **Google** | `POST /auth/staff/oauth/google` (ID token); mock UI | `POST /auth/oauth/google` | OTP-first sheet; Google **TBD** in older Ecom auth § | UI mock → demo session; `.../oauth/google/start` planned | — | TBD (Admin Q17) | +| **Email** | Password `POST /auth/staff/login` | `POST /auth/email/login` (+ optional register) | Magic-link sketched | Magic/OTP planned; demo persona login today | — | Email/invite primary (`/auth/doctor/login`) | +| **Phone OTP** | `phone/start` + `phone/verify` | `otp/request` + `otp/verify` (primary demo) | `otp/request` + `otp/verify` | SMS OTP planned | — | TBD | +| **Telegram** | Login Widget → `POST /auth/staff/telegram` | `POST /auth/oauth/telegram` | Telegram Mini App `POST /telegram/auth` sketched | Widget/Mini App callback planned | **Telegram user id enrollment** (identity) | Not primary | +| **Guest** | N/A (staff always auth); **guest customers** on orders | `POST /auth/guest` + guest-codes | Guest checkout by phone | N/A (org membership) | N/A | Creates guest **patients** | +| **Biometric** | — | Bind device key (Mob) | — | — | — | — | + +Path naming **diverges** (`/auth/staff/*` vs `/auth/otp/*` vs `/v1/auth/*`) — consolidation TBD; shared `authProvider` enum values should stay aligned. + +Env (Admin SPA): `VITE_GOOGLE_CLIENT_ID`, `VITE_TELEGRAM_BOT_USERNAME` (empty = mock UX). Server-only: bot token, OTP gateway, Google audience. + +--- + +## 4. Divergence & sync notes + +Accurate as of **2026-08-07** skim. Prefer fixing sheets over inventing a third truth here. + +| Topic | Divergence | Sync action | +| --- | --- | --- | +| API base host | Admin/Mob/Ecom use `api.gishen.example`; B2B OVERVIEW uses `api.gishenpharmacy.org` | Pick one production host | +| Money | Admin demos major ETB; Ecom sheet says cents optional | Platform-wide decision (Admin open Q1) | +| Auth path prefixes | Staff `/auth/staff/*`, Mob `/auth/otp/*`, B2B `/v1/auth/*` | Shared IdP façade TBD | +| Guest orders | Admin: staff/doctor `customerType=guest`; Mob/Ecom: guest session + `GSH-XXXX` codes | Document both flows; Admin POS `by-code` must match Mob code shape | +| Guest Admin endpoints | Mob lists `GET /admin/orders/by-code/:code` | Ensure Admin sheet mirrors when POS wire-up lands | +| Rx dosing | Admin structured `frequency`/`times`; Mob reminders separate resource | On approve, map Admin times → `/health/reminders` seed | +| Catalogue ID | Admin `Item` / `itemId`; retail `productId` / slug | Mapping table or shared SKU key | +| Multi-UOM | Admin full `uoms[]`; Mob/Ecom display `unit` string only | Expose sales UOM on catalog DTO when POS/cart need conversion | +| PDP clinical | Admin stores PDP fields on Item; Mob still derives much from `clinical.ts` overrides | Prefer catalogue PDP fields once API live; keep Mob overrides as fallback | +| Loyalty paths | Aligned idea (`/loyalty/me`); Admin config under `/admin/loyalty/*` | OK | +| Org types | Admin hospital/clinic for doctors + B2B commercial orgs | Keep `OrganisationType` shared | +| Roles naming | Admin snake staff roles; B2B `HR_ADMIN` SCREAMING; Ecom older `pharmacy_staff` names | Map in backend claims; don't rename UI mid-flight without sheet sync | +| Rider auth | Dispatch: Telegram enrollment; Ecom older Flutter rider JWT sketch **superseded** for v1 | Prefer Dispatch sheet | +| Mini App | Ecom documents endpoints; Mini App folder empty | Implement or mark out of scope | +| Doctor in B2B | Not a B2B portal role today | Open: doctor realm vs hospital org user | + +--- + +## 5. Links to deeper docs + +### Admin (this repo) + +| Doc | Purpose | +| --- | --- | +| [`docs/admin-backend-spec.md`](./admin-backend-spec.md) | Living Admin API/UI contract (primary depth) | +| [`README.md`](../README.md) | Runbook, demo accounts, deploy | +| `src/lib/dosingSchedule.ts` | Frequency → default times helpers | +| `src/config/navigation.ts` | Page registry | + +### Mob + +| Doc | Purpose | +| --- | --- | +| `Gishen-Mob/docs/BACKEND_SPEC.md` | Customer mobile API sheet | +| `Gishen-Mob/README.md` | Expo run + demo OTP | +| UI anchors | `app/(root)/auth/*`, `...(screens)/product/[id].tsx`, `prescription/*`, `checkout.tsx` | + +### Ecom + +| Doc | Purpose | +| --- | --- | +| `Gishen-Ecom/docs/backend.md` | Storefront + platform API proposal | +| `Gishen-Ecom/README.md` | Product proposal / demo | +| `Gishen-Ecom/docs/README.md` | Docs index | + +### B2B + +| Doc | Purpose | +| --- | --- | +| `Gishen-B2B/docs/README.md` | Standing rule + layout | +| `Gishen-B2B/docs/backend/OVERVIEW.md` | Tenancy, RBAC, clinical withhold | +| `Gishen-B2B/docs/backend/entities/` · `endpoints/` | Model + REST | +| `Gishen-B2B/docs/coordination/gishen-admin.md` | Admin ↔ B2B events | +| `Gishen-B2B/docs/coordination/gishen-ecom.md` | Checkout entitlement split | +| `Gishen-B2B/docs/coordination/open-items.md` | Cross-repo decisions | +| `Gishen-B2B/docs/features/INDEX.md` | Feature → pages map | + +### Related + +| Doc | Purpose | +| --- | --- | +| `Gishen-Dispatch-Bot/docs/BACKEND_SPEC.md` | Rider Telegram events / trips | +| `Gishen-Dispatch-Bot/docs/FEATURE_BRIEF.md` | Acceptance inventory | + +--- + +## 6. Cross-cutting open items (rolled up) + +Do not invent APIs here — track decisions: + +1. Unify API host + money units (cents vs major ETB). +2. Shared auth façade for Google / Email / Phone / Telegram across actors. +3. Guest order claim by phone OTP + PII retention. +4. Checkout entitlement-split owner (B2B recommends Ecom + shared calc). +5. Doctor IdP (dedicated realm vs hospital B2B user). +6. Multi-UOM ownership ERP vs Platform. +7. When Rx `times` fan out to Mob reminders. +8. Telegram Mini App scope vs empty repo. +9. Admin ↔ Mob `by-code` / POS scan contract end-to-end. + +Admin-local list: [`admin-backend-spec.md` § Open questions](./admin-backend-spec.md#open-questions). +B2B cross-repo: `Gishen-B2B/docs/coordination/open-items.md`. + +--- + +## Pre-push tooling + +Committed in **Gishen-Admin** only: + +| Piece | Path | Behavior | +| --- | --- | --- | +| Cursor rule | `.cursor/rules/update-master-spec-before-push.mdc` | Agents must refresh this master (and Admin sheet if needed) before push | +| Reminder script | `scripts/sync-master-spec-reminder.sh` | Prints Sources checklist + whether master looks stale vs `src/` / Admin sheet | +| Git hook | `.githooks/pre-push` | **Warns loudly** if significant paths changed since last push while master was not updated; does **not** call LLMs. Soft warn by default (`GISHEN_MASTER_SPEC_STRICT=1` to exit 1) | +| Enable hooks | `scripts/setup-githooks.sh` | Sets **local** `core.hooksPath=.githooks` for this repo only | + +```bash +# One-time per clone (optional but recommended) +bash scripts/setup-githooks.sh + +# Manual check anytime +bash scripts/sync-master-spec-reminder.sh +``` + +Do **not** silently rewrite global git config. Local `core.hooksPath` for this repository is intentional and reversible (`git config --unset core.hooksPath`). diff --git a/docs/admin-backend-spec.md b/docs/admin-backend-spec.md index ac8cab2..393c865 100644 --- a/docs/admin-backend-spec.md +++ b/docs/admin-backend-spec.md @@ -6,10 +6,13 @@ **Auth:** Bearer JWT for staff roles **Mocks:** Admin SPA uses in-memory mocks until `VITE_USE_MOCKS=false` and `VITE_API_BASE_URL` point here. +**Cross-app master:** [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) — product surface map, shared domain objects, auth matrix, and divergence notes vs Mob / Ecom / B2B. Refresh the master (and this sheet) before pushes; see its Update rule. + ## Changelog | Date | Change | | --- | --- | +| 2026-08-07 | Pointer to cross-app [`GISHEN-MASTER-SPEC.md`](./GISHEN-MASTER-SPEC.md) + pre-push refresh rule | | 2026-08-06 | Scaffold: conventions, roles, auth, empty module sections | | 2026-08-06 | Shell + RBAC + preferredLocale | | 2026-08-06 | Pharmacist: prescriptions, branch orders | diff --git a/scripts/setup-githooks.sh b/scripts/setup-githooks.sh new file mode 100755 index 0000000..1755d05 --- /dev/null +++ b/scripts/setup-githooks.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Enable in-repo hooks for this clone only (does not touch global git config). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +git config core.hooksPath .githooks +chmod +x .githooks/pre-push scripts/sync-master-spec-reminder.sh scripts/setup-githooks.sh 2>/dev/null || true + +echo "Set local core.hooksPath=.githooks for $(basename "$ROOT")" +echo "Pre-push will remind you to refresh docs/GISHEN-MASTER-SPEC.md" +echo "Unset with: git config --unset core.hooksPath" +echo "Strict block: GISHEN_MASTER_SPEC_STRICT=1 git push" diff --git a/scripts/sync-master-spec-reminder.sh b/scripts/sync-master-spec-reminder.sh new file mode 100755 index 0000000..304bc24 --- /dev/null +++ b/scripts/sync-master-spec-reminder.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# Reminder + staleness check for docs/GISHEN-MASTER-SPEC.md +# Does NOT call LLMs. Safe to run from pre-push or manually. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +MASTER="docs/GISHEN-MASTER-SPEC.md" +ADMIN_SPEC="docs/admin-backend-spec.md" + +RED=$'\033[1;31m' +YEL=$'\033[1;33m' +GRN=$'\033[1;32m' +BLD=$'\033[1m' +RST=$'\033[0m' + +echo "${BLD}Gishen master spec reminder${RST}" +echo " Master: ${MASTER}" +echo + +if [[ ! -f "$MASTER" ]]; then + echo "${RED}ERROR:${RST} Missing ${MASTER}" + echo " Create/update it before push. See docs/GISHEN-MASTER-SPEC.md Update rule." + exit 1 +fi + +echo "${BLD}Pre-push checklist${RST}" +echo " [ ] Changelog row if domain contract changed" +echo " [ ] Skim Mob / Ecom / B2B (and Dispatch if relevant) when folders exist" +echo " [ ] Sources stamp + Last reconciled bumped" +echo " [ ] ${ADMIN_SPEC} updated if Admin modules changed" +echo + +mtime_of() { + stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" +} + +master_mtime=$(mtime_of "$MASTER") +stale=0 +newest_src="" +newest_ts=$master_mtime + +consider() { + local path="$1" + [[ -e "$path" ]] || return 0 + local ts + ts=$(mtime_of "$path") + if (( ts > newest_ts )); then + newest_ts=$ts + newest_src="$path" + fi + if (( ts > master_mtime )); then + stale=1 + fi +} + +consider "$ADMIN_SPEC" +consider "package.json" +consider "README.md" + +# Sample recent TypeScript under src (portable; avoid head -z) +if [[ -d src ]]; then + while IFS= read -r f; do + [[ -n "$f" ]] || continue + consider "$f" + done < <(find src -type f \( -name '*.ts' -o -name '*.tsx' \) 2>/dev/null | head -n 200) +fi + +# Relative to upstream if available +RANGE="" +if git rev-parse --verify @{u} >/dev/null 2>&1; then + RANGE="@{u}..HEAD" +elif git rev-parse --verify origin/main >/dev/null 2>&1; then + RANGE="origin/main..HEAD" +elif git rev-parse --verify origin/master >/dev/null 2>&1; then + RANGE="origin/master..HEAD" +fi + +changed_significant=0 +master_in_range=0 +if [[ -n "$RANGE" ]]; then + if git diff --name-only "$RANGE" 2>/dev/null | grep -E '^(src/|docs/admin-backend-spec\.md|package\.json)' >/dev/null; then + changed_significant=1 + fi + if git diff --name-only "$RANGE" 2>/dev/null | grep -E '^docs/GISHEN-MASTER-SPEC\.md$' >/dev/null; then + master_in_range=1 + fi +fi + +# Working tree: uncommitted significant changes without master touch +if git status --porcelain 2>/dev/null | grep -E '^(A |M |M|A|\?\?) (src/|docs/admin-backend-spec\.md)' >/dev/null; then + if ! git status --porcelain 2>/dev/null | grep -E 'GISHEN-MASTER-SPEC\.md' >/dev/null; then + changed_significant=1 + fi +fi + +if (( stale == 1 )); then + echo "${YEL}WARN:${RST} ${MASTER} looks older than significant Admin sources." + echo " Newer example: ${newest_src:-unknown}" + echo " Refresh the master changelog + Sources stamp before push." +fi + +if (( changed_significant == 1 && master_in_range == 0 )); then + echo "${YEL}WARN:${RST} Significant Admin changes in push range, but ${MASTER} not updated in that range." + echo " Agents/humans: update master per Update rule, then recommit." +fi + +if (( stale == 0 && (changed_significant == 0 || master_in_range == 1) )); then + echo "${GRN}OK:${RST} No obvious master-spec staleness signals (still verify changelog if you changed domain contracts)." +fi + +echo +echo "Sibling skim paths (read-only):" +echo " ../Gishen-Mob/docs/BACKEND_SPEC.md" +echo " ../Gishen-Ecom/docs/backend.md" +echo " ../Gishen-B2B/docs/backend/OVERVIEW.md" +echo " ../Gishen-Dispatch-Bot/docs/BACKEND_SPEC.md" +echo + +if [[ "${GISHEN_MASTER_SPEC_STRICT:-0}" == "1" ]] && (( stale == 1 || (changed_significant == 1 && master_in_range == 0) )); then + echo "${RED}STRICT:${RST} GISHEN_MASTER_SPEC_STRICT=1 → exiting 1" + exit 1 +fi + +exit 0