@smartsoft001-mobilems/claude-plugins 2.88.0 → 2.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +27 -2
  2. package/package.json +1 -1
  3. package/plugins/mobilems/.claude-plugin/README.md +21 -2
  4. package/plugins/mobilems/.claude-plugin/plugin.json +1 -1
  5. package/plugins/mobilems/skills/mobilems-articles/SKILL.md +52 -20
  6. package/plugins/mobilems/skills/mobilems-articles/references/module-and-routes.md +24 -0
  7. package/plugins/mobilems/skills/mobilems-articles/scripts/check-translations.js +90 -0
  8. package/plugins/mobilems/skills/mobilems-articles/scripts/verify-module.sh +53 -0
  9. package/plugins/mobilems/skills/mobilems-devops-config/SKILL.md +141 -0
  10. package/plugins/mobilems/skills/mobilems-devops-config/references/docker.md +58 -0
  11. package/plugins/mobilems/skills/mobilems-devops-config/references/env-and-lint.md +59 -0
  12. package/plugins/mobilems/skills/mobilems-devops-config/references/github-hygiene.md +54 -0
  13. package/plugins/mobilems/skills/mobilems-devops-config/references/tooling.md +105 -0
  14. package/plugins/mobilems/skills/mobilems-devops-config/references/workflows.md +410 -0
  15. package/plugins/mobilems/skills/mobilems-devops-config/scripts/detect-foundation.sh +47 -0
  16. package/plugins/mobilems/skills/mobilems-devops-config/scripts/verify-devops.sh +96 -0
  17. package/plugins/mobilems/skills/mobilems-events/SKILL.md +27 -1
  18. package/plugins/mobilems/skills/mobilems-events/references/module-and-routes.md +24 -0
  19. package/plugins/mobilems/skills/mobilems-events/scripts/verify-module.sh +43 -0
  20. package/plugins/mobilems/skills/mobilems-object-collections/SKILL.md +46 -14
  21. package/plugins/mobilems/skills/mobilems-object-collections/references/lib-contents.md +11 -0
  22. package/plugins/mobilems/skills/mobilems-object-collections/references/module-and-routes.md +26 -0
  23. package/plugins/mobilems/skills/mobilems-object-collections/scripts/check-translations.js +90 -0
  24. package/plugins/mobilems/skills/mobilems-object-collections/scripts/verify-module.sh +54 -0
  25. package/plugins/mobilems/skills/mobilems-objects/SKILL.md +42 -10
  26. package/plugins/mobilems/skills/mobilems-objects/references/module-and-routes.md +24 -0
  27. package/plugins/mobilems/skills/mobilems-objects/scripts/check-translations.js +90 -0
  28. package/plugins/mobilems/skills/mobilems-objects/scripts/verify-module.sh +53 -0
  29. package/plugins/mobilems/skills/mobilems-partners/SKILL.md +30 -2
  30. package/plugins/mobilems/skills/mobilems-partners/references/module-and-routes.md +24 -0
  31. package/plugins/mobilems/skills/mobilems-partners/scripts/check-translations.js +90 -0
  32. package/plugins/mobilems/skills/mobilems-partners/scripts/verify-module.sh +53 -0
  33. package/plugins/mobilems/skills/mobilems-project-init/SKILL.md +68 -8
  34. package/plugins/mobilems/skills/mobilems-project-init/references/branding-reset.md +112 -0
  35. package/plugins/mobilems/skills/mobilems-project-init/references/claude-config.md +7 -0
  36. package/plugins/mobilems/skills/mobilems-project-init/references/config-providers.md +1 -1
  37. package/plugins/mobilems/skills/mobilems-project-init/references/qa-hardening.md +30 -30
  38. package/plugins/mobilems/skills/mobilems-project-init/references/shared-lib.md +42 -5
  39. package/plugins/mobilems/skills/mobilems-project-init/references/tailwind-styles.md +33 -23
  40. package/plugins/mobilems/skills/mobilems-project-init/scripts/verify-init.sh +62 -9
  41. package/plugins/mobilems/skills/mobilems-thematic-publications/SKILL.md +25 -1
  42. package/plugins/mobilems/skills/mobilems-thematic-publications/references/lib-contents.md +11 -0
  43. package/plugins/mobilems/skills/mobilems-thematic-publications/references/module-and-routes.md +26 -0
  44. package/plugins/mobilems/skills/mobilems-thematic-publications/scripts/verify-module.sh +43 -0
package/README.md CHANGED
@@ -1,11 +1,36 @@
1
1
  # @smartsoft001-mobilems/claude-plugins
2
2
 
3
- Claude Code plugins for MobileMS / MuzaCMS projects. Ships three variants:
3
+ Claude Code plugins for MobileMS / MuzaCMS projects. Ships four plugins:
4
4
 
5
- - `flow` — modern Angular 20+ projects (primary plugin)
5
+ - `mobilems` — the museum scaffolding skills (see below)
6
+ - `flow` — modern Angular 20+ projects (primary flow plugin)
6
7
  - `flow-legacy` — Angular 14 projects
7
8
  - `flow-external` — standalone Angular 6–11 projects
8
9
 
10
+ The package's only TypeScript export is `CLAUDE_PLUGINS_VERSION`; the plugins themselves are the
11
+ MD/JSON files under `src/plugins/**`, copied to `dist` as build assets.
12
+
13
+ ## `mobilems` skills
14
+
15
+ Run them in this order for a new museum app — `/smart:scaffold-nx-workspace` →
16
+ `mobilems-project-init` → the feature skills → `mobilems-devops-config`.
17
+
18
+ | Skill | Builds |
19
+ | --- | --- |
20
+ | `mobilems-project-init` | The MobilEMS foundation: deps, Tailwind, runtime env, `app.config` providers, shared lib, static pages, app shell, `.claude`. No feature modules. |
21
+ | `mobilems-objects` | `libs/museum-objects` + the `/obiekty` route |
22
+ | `mobilems-object-collections` | `libs/museum-collections` + the `/kolekcje` route |
23
+ | `mobilems-articles` | A parametrized (NAME + TEMPLATE) article module — news, education, … |
24
+ | `mobilems-thematic-publications` | `libs/museum-thematic-paths` + the `/sciezki-tematyczne` route |
25
+ | `mobilems-events` | `libs/museum-events` + the `/wydarzenia` route (booking flow) |
26
+ | `mobilems-partners` | `libs/museum-partners` + the `/partnerzy` route |
27
+ | `mobilems-devops-config` | The ops layer: Dockerfile, GitHub Actions, update tooling, env/lint/node, `.github` hygiene |
28
+ | `mobilems-docs-reader` | Answers MuzaCMS indexer-API questions from a freshly fetched OpenAPI spec (read-only) |
29
+
30
+ Every scaffolding skill opens with a **Framework first — MANDATORY** section pointing at the
31
+ [framework API catalog](../angular/README.md); they reuse framework exports rather than
32
+ re-implementing them.
33
+
9
34
  ## MuzaCMS API access (required for `mobilems-docs-reader` skill)
10
35
 
11
36
  The `mobilems-docs-reader` skill answers questions about the MuzaCMS / MobileMS indexer API by fetching its Swagger spec from a live MuzaCMS instance. The MuzaCMS docs page is behind a Symfony form-login, so the skill needs credentials. The refresh script lives next to the skill (`plugins/<variant>/skills/mobilems-docs-reader/refresh-mobilems-api-spec.sh`) and caches fetches per Claude Code session (with an 8-hour TTL) — see [Caching](#caching) below for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartsoft001-mobilems/claude-plugins",
3
- "version": "2.88.0",
3
+ "version": "2.90.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,14 +1,33 @@
1
1
  # Mobilems Plugin for Claude Code
2
2
 
3
- MobileMS-specific skills for the MuzaCMS / MobileMS indexer API. This plugin is a thin complement to the generic `flow@smart-pro` workflow plugins — it provides skills that are tied to the MobileMS backend.
3
+ MobileMS-specific skills: the museum app scaffolding suite plus the MuzaCMS / MobileMS indexer API reader. This plugin complements the generic `flow@smart-pro` workflow plugins — it provides the skills tied to the MobileMS backend and to the museum app architecture.
4
4
 
5
5
  ## Contents
6
6
 
7
+ ### Scaffolding
8
+
9
+ Run order for a new museum app: `/smart:scaffold-nx-workspace` → `mobilems-project-init` → the feature skills (any order, any subset) → `mobilems-devops-config`.
10
+
11
+ | Type | Name | Purpose |
12
+ |------|------|---------|
13
+ | Skill | `mobilems-project-init` | The MobilEMS foundation — deps, Tailwind, SSR + translations runtime, `app.config` providers, shared lib, static pages, app shell, `.claude`. Produces NO feature modules |
14
+ | Skill | `mobilems-objects` | Museum objects module — `libs/museum-objects` (domain + shell/angular) + the `/obiekty` route |
15
+ | Skill | `mobilems-object-collections` | Collections module — `libs/museum-collections` + the `/kolekcje` route |
16
+ | Skill | `mobilems-articles` | A parametrized (NAME + TEMPLATE) article-backed content module; run once per content type |
17
+ | Skill | `mobilems-thematic-publications` | Thematic paths — `libs/museum-thematic-paths` + the `/sciezki-tematyczne` route |
18
+ | Skill | `mobilems-events` | Events + booking flow — `libs/museum-events` + the `/wydarzenia` route |
19
+ | Skill | `mobilems-partners` | Partners — `libs/museum-partners` + the `/partnerzy` route |
20
+ | Skill | `mobilems-devops-config` | The ops layer — Dockerfile, GitHub Actions, update tooling, `.env.dev`/lint/node, `.github` hygiene |
21
+
22
+ Each of these opens with a **Framework first — MANDATORY** section pointing at the [Framework API index](https://github.com/smartsoft001/mobilems-framework/blob/main/README.md#-framework-api): reuse framework exports (`MasonryGridComponent`, `FiltersBaseComponent` / `FiltersContext`, `FileUrlService`, `SeoService`, …) rather than re-implementing them. Their bundled `scripts/verify-*.sh` fail the run on hand-rolled masonry grids and on filters that bypass `FiltersBaseComponent`.
23
+
24
+ ### API docs
25
+
7
26
  | Type | Name | Purpose |
8
27
  |------|------|---------|
9
28
  | Skill | `mobilems-docs-reader` | Answer questions about the MuzaCMS / MobileMS indexer OpenAPI spec; fetches a fresh copy via the bundled refresh script |
10
29
 
11
- The skill ships with `scripts/refresh-mobilems-api-spec.sh`, which logs into `https://develop-indexer.mobilems.pl` using `MOBILEMS_LOGIN` / `MOBILEMS_PASSWORD` (read from env or `.env`) and writes the spec to `shared-assets/mobilems-api.json` inside the installed package. The script caches the fetched spec per Claude Code session (8h TTL).
30
+ That skill ships with `scripts/refresh-mobilems-api-spec.sh`, which logs into `https://develop-indexer.mobilems.pl` using `MOBILEMS_LOGIN` / `MOBILEMS_PASSWORD` (read from env or `.env`) and writes the spec to `shared-assets/mobilems-api.json` inside the installed package. The script caches the fetched spec per Claude Code session (8h TTL).
12
31
 
13
32
  ## Installation
14
33
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "mobilems",
3
3
  "description": "MobilEMS-specific Claude Code skills (MuzaCMS / MobileMS indexer API)",
4
- "version": "2.88.0"
4
+ "version": "2.90.0"
5
5
  }
@@ -39,9 +39,33 @@ gave them). The first two are the essentials:
39
39
 
40
40
  Run `scripts/detect-init.sh <workspace-root>`. It confirms the MobilEMS foundation is present
41
41
  (`SharedModule.forRoot` in `app.config.ts`, a populated shared-lib barrel). If not, tell the user to
42
- run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`; reference uses `@msr`)
42
+ run `/mobilems:project-init` first. Hold `<PREFIX>` (from `tsconfig.base.json`)
43
43
  and the reference repo path (same one init used).
44
44
 
45
+ ## Framework first — MANDATORY
46
+
47
+ Read these before writing ANY component, service, pipe, directive or helper, and reuse what they
48
+ list — re-implementing framework code is a defect, not a shortcut:
49
+
50
+ 0. [**The Framework API index**](https://github.com/smartsoft001/mobilems-framework/blob/main/README.md#-framework-api) — the map of every package to
51
+ its catalog. Start here when you do not already know which package owns what.
52
+ 1. The framework API catalog — `packages/shared/angular/README.md` in this repo, installed as
53
+ `node_modules/@smartsoft001-mobilems/angular/README.md` in a museum app ([browse](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/angular/README.md)).
54
+ This is the path `scripts/verify-module.sh` names when it fails.
55
+ 2. [`packages/articles/README.md`](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/articles/README.md) — this module's package catalog
56
+ 3. `libs/shared/angular/README.md` — the app's own shared lib (built by `mobilems-project-init`)
57
+
58
+ | Do not hand-roll | Use |
59
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
60
+ | a masonry grid (`new Masonry(...)`, per-page masonry JS/CSS) | `<smart-mobilems-masonry-grid>` (`MasonryGridComponent`) with a `#masonryContentTpl`; the `Masonry` provider only feeds it |
61
+ | filter state / hand-built `query` arrays | `FiltersBaseComponent<Article>` + its `FiltersContext` accessors (`searchText`, `authors`, `cycles`, `publicationDateFrom/To`, `page`, `sort`, …) |
62
+ | list/detail plumbing (facade wiring, id-from-route, PDF) | the `ArticlesListComponent` / `ArticlesItemComponent` base classes |
63
+ | media URLs, meta/OG tags, filter dictionaries, modals, WCAG | `FileUrlService`, `SeoService`/`MetaService`, `DictionaryService`, `ModalService`, `WcagService` |
64
+ | a content-block renderer | `WidgetComponent` + the `ArticleWidgetType` map |
65
+
66
+ Anything the framework genuinely lacks goes into `libs/shared/angular` **once** (and into its
67
+ README) — never a per-module copy. `scripts/verify-module.sh` fails the run on hand-rolled masonry.
68
+
45
69
  ## Steps
46
70
 
47
71
  1. **Gather the parameters** above. Ask for the **name** and **template** at minimum; default `route`
@@ -54,11 +78,16 @@ and the reference repo path (same one init used).
54
78
  Also add any **extra third-party packages the copied pages import** that aren't already in the
55
79
  workspace — e.g. the reference list page uses `lodash-decorators` (`@debounce`); grep the pages you
56
80
  copy for non-`@angular`/`@smartsoft001` imports and add the missing ones.
57
- 3. **Library** — create `libs/museum-<name>/{domain,shell/angular}` per `references/lib-contents.md`:
58
- the thin domain entity (`Museum<Name> extends Article`), the shell (`Museum<Name>Module`, routes,
59
- `pages/{list,item}`, services, and — if the module keeps feature state — `+state/<name>.reducer.ts`),
60
- threading `<PREFIX>` and the chosen name/entity. Add the two `tsconfig.base.json` paths
61
- (`@<PREFIX>/museum-<name>/domain` + `/shell/angular`).
81
+ 3. **Library** — scaffold `libs/museum-news` with this package's Nx generator (deterministic — do
82
+ NOT copy files by hand when the generator is available):
83
+ ```bash
84
+ npx nx g @smartsoft001-mobilems/articles-shell-angular:museum-module \
85
+ --referenceRoot=<reference-repo> --prefix=<PREFIX>
86
+ ```
87
+ It copies the lib from the reference, threads `@<PREFIX>/`, adds both `tsconfig.base.json`
88
+ paths, normalizes `project.json` paths and copies the module's translation groups (pl+en).
89
+ Fallback (older framework line without the generator): do the same by hand per
90
+ `references/lib-contents.md`. For a NON-news article module (different `<name>`/entity), the generator's `news` output is the starting point — rename per `references/lib-contents.md` after generating, or fall back to the manual path entirely.
62
91
  4. **Module + route** — see `references/module-and-routes.md`: `Museum<Name>Module` wraps
63
92
  `ArticlesSharedModule.forFeature` with `type: Article`, the chosen `entity`, and
64
93
  `baseQuery: [{ key: 'template', value: '<template>', type: '=', hidden: true }]`, plus the `Masonry`
@@ -71,29 +100,32 @@ and the reference repo path (same one init used).
71
100
  and list cards (`<name>-grid-item`, `<name>-list-item`) in `libs/shared/angular` and export them; if
72
101
  the module renders article-gallery content blocks, also build the article gallery widget and register
73
102
  it in the widget content-type map (build once, idempotent across article modules).
74
- 6. **Verify** — run `scripts/verify-module.sh <workspace-root> <PREFIX> <name> [template]`: asserts the
103
+ 6. **Translations** — copy the module's translation groups (`NEWS` + `ARTICLES` for the aktualności instance (override with `TRANS_GROUPS` when the module uses different groups)) from the reference
104
+ repo's `apps/web/public/translations.json` into the app's file, for BOTH `pl` and `en`.
105
+ Complete group = every reference key present; every key referenced in the module's code
106
+ must exist in both languages — a raw `GROUP.key` rendered in the UI is a defect.
107
+ 7. **Verify** — run `REF_ROOT=<reference-repo> scripts/verify-module.sh <workspace-root> <PREFIX> <name> [template]`: asserts the
75
108
  library exists, the `forFeature` carries the chosen `entity` (and template, if given), the route is
76
109
  registered, and the article card shared components are built and exported. Fix and re-run.
77
110
 
78
- ## Known QA pitfalls (from live deployments — fix while copying, not after)
111
+ ## Known pitfalls (fix while copying, not after)
79
112
 
80
- Each of these was a real Linear bug on a museum project built with this skill (Ostrowiec MOB-962,
81
- Tomaszów MOB-957/993). The cross-cutting rules live in `mobilems-project-init`
82
- `references/qa-hardening.md`; these are the article-module-specific ones:
113
+ The cross-cutting rules live in `mobilems-project-init` `references/qa-hardening.md`; these are the
114
+ article-module-specific ones:
83
115
 
84
116
  - **Author filter dictionary key is lowercase `authors`.** `dictionaryKey="authors"` routes to
85
117
  `api/article/authors/...`; `"Author"` silently falls through to `api/dictionary/Author/...` →
86
- HTTP 409 and an empty options list (one-character bug that shipped to QA).
87
- - **alt from CMS titles must strip HTML.** The detail header image shipped with
118
+ HTTP 409 and an empty options list.
119
+ - **alt from CMS titles must strip HTML.** Otherwise the detail header image gets
88
120
  `alt="<p>Tytuł</p>"` — pipe the title through `smartRemoveHtml` before binding it to `alt`.
89
- - **Detail page structure:** an `h1` (article title — copied reference had only `h2`), breadcrumbs
90
- (`/ Artykuły / {tytuł}`), and gallery image captions rendered as text, NOT `h3` (an `h1 → h3`
91
- jump is a recurring axe-core finding).
92
- - **List item field order per mockup:** date ABOVE the title (implementations kept shipping it at
93
- the bottom), keywords as inline links styled per the design, and mockup-exact sort labels
121
+ - **Detail page structure:** an `h1` (article title — a copied reference may have only `h2`),
122
+ breadcrumbs (`/ Artykuły / {tytuł}`), and gallery image captions rendered as text, NOT `h3` (an
123
+ `h1 → h3` jump is an axe-core finding).
124
+ - **List item field order per mockup:** date ABOVE the title, keywords as inline links styled per
125
+ the design, and mockup-exact sort labels
94
126
  ("Sortowanie: Od najnowszych", not "Sortuj: Data publikacji - od najnowszych").
95
- - **List spacing:** in list view the items need a real gap (`gap-4` fixed the "too close together"
96
- QA bug).
127
+ - **List spacing:** in list view the items need a real gap (`gap-4`); without it they read as one
128
+ block.
97
129
  - **Search debounce 250 ms** (`@debounce(250)`), total count + shared `NextPageComponent` on the
98
130
  list — consistent with objects/collections.
99
131
  - **Warning-clean templates:** remove needless `?.` on non-nullable signals (NG8107) and unused
@@ -61,6 +61,30 @@ import { routes } from './<name>.routes';
61
61
  export class Museum<Name>Module {}
62
62
  ```
63
63
 
64
+ > **The `Masonry` provider exists only to feed the framework's `MasonryGridComponent`.** It is DI
65
+ > plumbing, not a grid — never call `new Masonry(...)`, keep a `MasonryInstance`, or re-implement the
66
+ > layout/`reloadItems` dance in a page component. A hand-rolled CSS-grid or column layout is the same
67
+ > defect. `scripts/verify-module.sh` fails the run on both.
68
+
69
+ The list page renders the grid like this — copy this shape, it is the required one:
70
+
71
+ ```html
72
+ <smart-mobilems-masonry-grid [list]="list()" [options]="{ gutter: 24 }">
73
+ <ng-template #masonryContentTpl>
74
+ <div class="masonry-grid-sizer"></div>
75
+ @for (item of list(); track item.id) {
76
+ <div class="masonry-item"><smart-<name>-grid-item [item]="item" /></div>
77
+ }
78
+ </ng-template>
79
+ </smart-mobilems-masonry-grid>
80
+ ```
81
+
82
+ `.masonry-grid-sizer` and `.masonry-item` are required — they are the component's default
83
+ `columnWidth` / `itemSelector`. The card (shared-components.md) is the only module-specific part. Inputs,
84
+ public methods and the SSR provider are documented in the framework catalog,
85
+ [`packages/shared/angular/README.md`](https://github.com/smartsoft001/mobilems-framework/blob/main/packages/shared/angular/README.md#masonrygridcomponent)
86
+ (installed as `node_modules/@smartsoft001-mobilems/angular/README.md`).
87
+
64
88
  The **`baseQuery`** is what scopes the module to its content: `key` is always `'template'`, `type` is
65
89
  `'='`, `hidden` is `true`, and `value` is the module's `<template>` (the API then returns only records
66
90
  matching that template). Change only the `value` between modules.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-translations.js "<GROUP GROUP …>" [reference-root]
4
+ *
5
+ * Run from the workspace root (verify-module.sh cd's there). Verifies the
6
+ * module's translation groups in apps/web/public/translations.json:
7
+ * 1. each group exists and is non-empty in BOTH pl and en,
8
+ * 2. every literal GROUP.* key referenced in libs/ or apps/web/src exists
9
+ * in both languages (dead references = defect),
10
+ * 3. with a reference root: every reference key of the group exists in the
11
+ * generated file — missing keys are listed (the module is incomplete).
12
+ * Prints PASS/FAIL lines in the verify-module.sh idiom; exit 1 on any FAIL.
13
+ */
14
+ const fs = require("fs");
15
+
16
+ const [groupsArg, refRoot] = process.argv.slice(2);
17
+ const groups = (groupsArg || "").split(/\s+/).filter(Boolean);
18
+ if (!groups.length) { console.log(" FAIL: no translation groups given"); process.exit(1); }
19
+
20
+ const TRANS = "apps/web/public/translations.json";
21
+ let fail = 0;
22
+ const pass = (m) => console.log(` PASS: ${m}`);
23
+ const bad = (m) => { console.log(` FAIL: ${m}`); fail = 1; };
24
+
25
+ const flat = (obj, prefix) => Object.entries(obj || {}).flatMap(([k, v]) =>
26
+ v && typeof v === "object" ? flat(v, `${prefix}.${k}`) : [`${prefix}.${k}`]);
27
+
28
+ const readTrans = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf8")).data.translations; } catch { return null; } };
29
+
30
+ const gen = readTrans(TRANS);
31
+ if (!gen) { bad(`cannot read ${TRANS}`); process.exit(1); }
32
+
33
+ // 1. groups present + non-empty in both languages
34
+ const genKeys = { pl: new Set(), en: new Set() };
35
+ for (const lang of ["pl", "en"]) {
36
+ for (const g of groups) {
37
+ const keys = flat(gen[lang]?.[g], g);
38
+ keys.forEach((k) => genKeys[lang].add(k));
39
+ if (keys.length) pass(`${g} present in ${lang} (${keys.length} keys)`);
40
+ else bad(`${g} missing or empty in ${lang}`);
41
+ }
42
+ }
43
+
44
+ // 2. dead references — literal GROUP.* keys used in sources must exist.
45
+ // Dynamic tails (`GROUP.${x}`) don't match the literal regex, and a matched
46
+ // PREFIX that is a parent of existing keys counts as present.
47
+ const walk = (dir) => {
48
+ let out = [];
49
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
50
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
51
+ const p = `${dir}/${e.name}`;
52
+ if (e.isDirectory()) out = out.concat(walk(p));
53
+ else if (/\.(ts|html)$/.test(e.name)) out.push(p);
54
+ }
55
+ return out;
56
+ };
57
+ let src = "";
58
+ for (const d of ["libs", "apps/web/src"]) {
59
+ if (fs.existsSync(d)) src += walk(d).map((f) => { try { return fs.readFileSync(f, "utf8"); } catch { return ""; } }).join("\n");
60
+ }
61
+ const re = new RegExp(`\\b(?:${groups.join("|")})(?:\\.[A-Za-z0-9_]+)+`, "g");
62
+ const used = new Set(src.match(re) || []);
63
+ const dead = { pl: [], en: [] };
64
+ for (const key of used) {
65
+ for (const lang of ["pl", "en"]) {
66
+ const ks = genKeys[lang];
67
+ if (!ks.has(key) && ![...ks].some((k) => k.startsWith(`${key}.`))) dead[lang].push(key);
68
+ }
69
+ }
70
+ for (const lang of ["pl", "en"]) {
71
+ if (dead[lang].length) bad(`${lang}: keys referenced in code but missing in ${TRANS}: ${dead[lang].slice(0, 8).join(", ")}${dead[lang].length > 8 ? ` (+${dead[lang].length - 8})` : ""}`);
72
+ else pass(`no dead ${lang} references (${used.size} literal keys checked)`);
73
+ }
74
+
75
+ // 3. completeness vs reference
76
+ if (refRoot) {
77
+ const ref = readTrans(`${refRoot}/apps/web/public/translations.json`);
78
+ if (!ref) bad(`cannot read reference translations under ${refRoot}`);
79
+ else {
80
+ for (const lang of ["pl", "en"]) {
81
+ const missing = groups.flatMap((g) => flat(ref[lang]?.[g], g)).filter((k) => !genKeys[lang].has(k));
82
+ if (missing.length) bad(`${lang}: ${missing.length} reference keys missing: ${missing.slice(0, 8).join(", ")}${missing.length > 8 ? " …" : ""}`);
83
+ else pass(`${lang}: all reference keys covered`);
84
+ }
85
+ }
86
+ } else {
87
+ console.log(" SKIP: no reference root given — completeness vs reference not checked");
88
+ }
89
+
90
+ process.exit(fail);
@@ -11,6 +11,7 @@ ROOT="${1:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
11
11
  PREFIX="${2:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
12
12
  NAME="${3:?usage: verify-module.sh <workspace-root> <PREFIX> <name> [template]}"
13
13
  TEMPLATE="${4:-}"
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
14
15
  cd "$ROOT" 2>/dev/null || { echo "ERROR: no such dir: $ROOT" >&2; exit 2; }
15
16
 
16
17
  LIBF="museum-$NAME"
@@ -56,6 +57,58 @@ for p in articles-domain articles-shell-angular articles-shell-dtos; do
56
57
  grep -q "@smartsoft001-mobilems/$p" package.json && pass "$p dep" || bad "$p dep missing"
57
58
  done
58
59
 
60
+ echo "== Framework first (packages/shared/angular/README.md) =="
61
+ LIBDIR="libs/$LIBF/shell/angular/src/lib"
62
+ # 1. masonry belongs to the framework's MasonryGridComponent — no module-level instances
63
+ MAS="$(grep -rlnE "new Masonry\(|MasonryInstance" libs apps/web/src 2>/dev/null || true)"
64
+ if [ -n "$MAS" ]; then
65
+ bad "hand-rolled masonry — render grids with <smart-mobilems-masonry-grid> (MasonryGridComponent):"
66
+ printf ' %s\n' $MAS >&2
67
+ else
68
+ pass "no hand-rolled masonry instances"
69
+ fi
70
+ # 2. the Masonry provider exists ONLY to feed MasonryGridComponent. Providing it without ever
71
+ # rendering the component means the grid was hand-rolled some other way (CSS columns, flex, …),
72
+ # which the `new Masonry(` grep above cannot see.
73
+ if grep -rq "provide: Masonry" "$LIBDIR" 2>/dev/null; then
74
+ if grep -rq "smart-mobilems-masonry-grid" "$LIBDIR" 2>/dev/null; then
75
+ pass "grids render via <smart-mobilems-masonry-grid>"
76
+ else
77
+ bad "module provides Masonry but never renders <smart-mobilems-masonry-grid> — hand-rolled grid"
78
+ fi
79
+ fi
80
+ # 3. a list page must render through the framework grid
81
+ LISTP="$(find "$LIBDIR/pages" -maxdepth 1 -type d -name 'list*' 2>/dev/null || true)"
82
+ if [ -n "$LISTP" ]; then
83
+ if grep -rq "smart-mobilems-masonry-grid" $LISTP 2>/dev/null; then
84
+ pass "list page renders via <smart-mobilems-masonry-grid>"
85
+ else
86
+ bad "list page does not render <smart-mobilems-masonry-grid> (README.md#masonrygridcomponent)"
87
+ fi
88
+ fi
89
+ # 4. filters must EXTEND the framework base class — `context.` alone proves nothing, and a
90
+ # differently-named dir must not silently skip the check
91
+ FILT="$(find "$LIBDIR/components" -maxdepth 1 -type d \( -name 'filters*' -o -name 'search*' -o -name 'facet*' \) 2>/dev/null || true)"
92
+ if [ -n "$FILT" ]; then
93
+ if grep -rqE "FiltersBaseComponent" $FILT 2>/dev/null; then
94
+ pass "filters use the framework filter API"
95
+ else
96
+ bad "filters do not extend FiltersBaseComponent — no ad-hoc query building:"
97
+ printf ' %s\n' $FILT >&2
98
+ fi
99
+ else
100
+ pass "no filters component in this module"
101
+ fi
102
+
103
+ echo "== Translations (REF_ROOT=${REF_ROOT:-unset}) =="
104
+ # Groups: present in pl+en, no dead references, complete vs the reference repo
105
+ # (export REF_ROOT=<reference-repo-root> to enable the completeness diff).
106
+ if node "$SCRIPT_DIR/check-translations.js" "${TRANS_GROUPS:-NEWS ARTICLES}" "${REF_ROOT:-}"; then
107
+ pass "translation groups complete"
108
+ else
109
+ bad "translation gaps — fix the FAIL lines above"
110
+ fi
111
+
59
112
  echo
60
113
  if [ "$fail" = 0 ]; then echo "VERIFY: all checks passed."; else echo "VERIFY: FAILED — fix the items above and re-run." >&2; fi
61
114
  exit "$fail"
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: mobilems-devops-config
3
+ description: Configure the ops / deployment layer of a MobilEMS museum web-app — Dockerfile, GitHub Actions CI/CD (development, e2e, push-to-client, ai-plan, ai-impl, cached-deps action), dependency-update tooling (tools/scripts/update.js + npm "update"), env/lint/node config (.env.dev, .eslintignore, .nvmrc), and .github hygiene (removing or relocating skills/agents/prompts that scaffolding left in .github). Use whenever the user wants to add / scaffold / fill in Docker, CI/CD, GitHub Actions, the deployment pipeline, or update tooling for a museum app, "skonfiguruj docker / github actions / ci dla projektu muzealnego", or bring an existing museum app's ops config up to standard. Idempotent — creates what's missing, reports drift, never silently overwrites. Requires the MobilEMS foundation (mobilems-project-init) to exist first. Does NOT touch SSR / translations / app.config (project-init's job) or feature modules.
4
+ user-invocable: true
5
+ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
6
+ ---
7
+
8
+ # MobilEMS devops config
9
+
10
+ Install the MobilEMS museum app **ops / deployment layer** — the configuration that lives outside
11
+ the Angular app code: the Docker image, the GitHub Actions CI/CD pipeline, the dependency-update
12
+ tooling, and the env / lint / node-version files. This is the layer that `scaffold-nx-workspace`
13
+ (the generic Nx toolchain) and `mobilems-project-init` (the app-runtime foundation) leave out.
14
+
15
+ This skill is **self-contained**: the canonical file contents live in `references/` and are
16
+ written directly, so it works on a brand-new museum app (e.g. Łowicz) with no reference repo
17
+ present. It is also **idempotent and additive** — it creates what is missing and, when a file
18
+ already exists but differs from the canonical template, it reports the drift and asks before
19
+ overwriting. It never silently clobbers a customized file.
20
+
21
+ ## Scope
22
+
23
+ **In scope (this skill):**
24
+
25
+ - `apps/web/Dockerfile` (+ a recommended `.dockerignore`)
26
+ - `.github/workflows/`: `development.yml`, `e2e.yml`, `push-to-client.yml`, `ai-plan.yml`,
27
+ `ai-impl.yml`, `ci.yml.disabled`, and the `.github/actions/cached-deps/action.yml` composite
28
+ - `.github` hygiene — agent config (`skills/`, `agents/`, `prompts/`, …) does not belong in
29
+ `.github`; this skill removes or relocates it
30
+ - `tools/scripts/update.js` + the `package.json` `update` / `postinstall` scripts
31
+ - `.env.dev`, `.eslintignore`, `.nvmrc` — and aligning the node version across `.nvmrc`,
32
+ `Dockerfile`, and the workflows
33
+
34
+ **Out of scope (handled elsewhere):**
35
+
36
+ - SSR (`server.ts`, `/env.js`, `/api/translations`), translations runtime, Tailwind, and
37
+ `app.config.ts` — these belong to **`mobilems-project-init`**. This skill only _verifies_ they
38
+ exist (precondition) and tells the user to run project-init if they don't.
39
+ - Feature modules — the `mobilems-<module>` skills.
40
+ - The companion **`<project>-deploy`** Kubernetes repo (scaffolded separately from
41
+ `mobilems-muzeum-template-deploy`). Mention it as a follow-up; do not create it here.
42
+
43
+ ## Steps
44
+
45
+ ### 0. Precondition — confirm a MobilEMS foundation
46
+
47
+ Run `scripts/detect-foundation.sh <workspace-root>`. It checks this is a MobilEMS museum app:
48
+ `apps/web` exists, SSR is set up (`apps/web/src/server.ts` with `/api/translations`),
49
+ `@smartsoft001-mobilems/*` is in `package.json`, and `apps/web/public/translations.json` exists.
50
+ If the SSR / translations foundation is missing, **stop** and tell the user to run
51
+ `/mobilems:mobilems-project-init` first — this skill only adds the ops layer on top.
52
+
53
+ ### 1. Collect parameters (`AskUserQuestion`, with smart defaults)
54
+
55
+ - **Node version** — default to the existing `.nvmrc` if present; otherwise read the node version
56
+ already used in `apps/web/Dockerfile` / workflows; otherwise prompt (fall back to the team
57
+ standard `24.5.0`). This one value threads into `.nvmrc`, the Dockerfile base image, and every
58
+ workflow's `node-version`.
59
+ - **`.env.dev` values** — `API_URL`, `SITE_URL`, `MULTIMEDIA_URL` (and `APP_VERSION`). Read them
60
+ from an existing `.env` / `.env.dev`, or from `apps/web/src/environments/environment.prod.ts`
61
+ (`apiUrl` / `multimediaUrl`), or prompt. Never invent client URLs silently.
62
+
63
+ ### 2. Docker
64
+
65
+ Per `references/docker.md`: write `apps/web/Dockerfile`, threading the node version into the base
66
+ image (`node:<version>-alpine`). Add the recommended `.dockerignore` (the reference repo has none,
67
+ but it keeps the buildx context small). If a Dockerfile already exists and differs, show a diff and
68
+ ask before overwriting.
69
+
70
+ ### 3. GitHub Actions
71
+
72
+ Per `references/workflows.md`: write each workflow and the composite action, threading the node
73
+ version:
74
+
75
+ - `.github/workflows/development.yml` — build + Docker buildx push to `dockerregistry.mobilems.pl`
76
+ - auto-commit/tag + dispatch `deploy.yml` to the deploy repo.
77
+ - `.github/workflows/e2e.yml` — Playwright (chromium + webkit) + report artifact.
78
+ - `.github/workflows/push-to-client.yml` — GitLab mirror push + OpenVPN + SSH `docker stack deploy`.
79
+ - `.github/workflows/ai-plan.yml` / `ai-impl.yml` — call the shared
80
+ `smartsoft001/github-actions-shared` reusable workflows.
81
+ - `.github/workflows/ci.yml.disabled` — Nx-Cloud lint/test/build/e2e template, kept disabled.
82
+ - `.github/actions/cached-deps/action.yml` — composite: setup-node + cache `node_modules` + `npm ci`.
83
+
84
+ Then **print the required GitHub secrets table** from `references/workflows.md` (GitLab registry,
85
+ deploy dispatch, OpenVPN/SSH, Claude/Linear). These are configured in the repo's Actions settings,
86
+ **never** committed.
87
+
88
+ For each existing-but-divergent workflow (e.g. a stale Node-20 `ci.yml`), report it and ask before
89
+ overwriting — do not silently replace a workflow the team may have customized.
90
+
91
+ ### 3b. `.github` hygiene — nothing but GitHub config in `.github`
92
+
93
+ Per `references/github-hygiene.md`: `.github/skills/`, `.github/agents/`, `.github/prompts/`,
94
+ `.github/instructions/`, `.github/chatmodes/` and `.github/copilot-instructions.md` are scaffolding
95
+ leftovers in the Copilot layout — agent config targets the Claude Code architecture and belongs under
96
+ `.claude/` (`skills/`, `agents/`, `commands/`, `settings.json`). Classify each entry, then:
97
+
98
+ - **delete** what duplicates a plugin already enabled in `.claude/settings.json` — the whole `nx-*`
99
+ family and `monitor-ci` come from `nx@nx-claude-plugins`;
100
+ - **move** anything genuinely project-specific into `.claude/` (`skills/`, `agents/`, `commands/`)
101
+ with `git mv`;
102
+ - keep `workflows/`, `actions/` and GitHub's own metadata.
103
+
104
+ Always show the delete/move plan and **ask before deleting**. Report what was removed or relocated in
105
+ the final summary.
106
+
107
+ ### 4. Update tooling
108
+
109
+ Per `references/tooling.md`: write `tools/scripts/update.js`; ensure `package.json` has
110
+ `"update": "node ./tools/scripts/update.js"` and `"postinstall": "husky install"` (merge into the
111
+ existing `scripts` block — do not drop existing scripts).
112
+
113
+ ### 5. Env / lint / node
114
+
115
+ Per `references/env-and-lint.md`: write `.env.dev` (with the collected URLs), `.eslintignore`, and
116
+ `.nvmrc`. Confirm the node version is identical across `.nvmrc`, `apps/web/Dockerfile`, and all
117
+ workflows; fix any mismatch.
118
+
119
+ ### 6. Verify
120
+
121
+ Run `scripts/verify-devops.sh <workspace-root> <NODE_VERSION>`. It asserts every artifact landed and
122
+ that the node version is consistent across `.nvmrc` / Dockerfile / workflows. Fix and re-run until it
123
+ passes. Print a final summary of what was **created** vs **already present** vs **drift reported**.
124
+ Gate `npm install` behind an explicit ask — skip it during self-validation / eval.
125
+
126
+ ### 7. Follow-up note
127
+
128
+ Remind the user that production deployment also needs the companion **`<project>-deploy`** repo
129
+ (Kubernetes/Kustomize, from `mobilems-muzeum-template-deploy`) and the GitHub secrets from step 3 —
130
+ both are separate, manual follow-ups outside this skill.
131
+
132
+ ## Notes
133
+
134
+ - Docker-only deployment (no nginx / docker-compose); SSR served by `node server/server.mjs` on
135
+ port 80.
136
+ - All client-specific values stay as **GitHub Actions secrets** — the workflow files contain only
137
+ `${{ secrets.* }}` references, never literals.
138
+ - Bundled `references/` + `scripts/` ship automatically (the whole `src/plugins/**` tree is copied
139
+ to dist). Do NOT hand-edit `plugin.json` — the version is auto-synced at build.
140
+ - Run order for a fresh app: `/smart:scaffold-nx-workspace` → `/mobilems:mobilems-project-init` →
141
+ feature skills → **`/mobilems:mobilems-devops-config`**.
@@ -0,0 +1,58 @@
1
+ # Docker
2
+
3
+ Docker-only deployment (no nginx, no docker-compose). The image runs the Angular SSR server
4
+ (`node server/server.mjs`) on port 80. The application is built in CI **before** the image is
5
+ built; the Dockerfile only installs production deps and copies the prebuilt `dist/apps/web`.
6
+
7
+ `{{NODE_VERSION}}` = the node version collected in step 1 (default from `.nvmrc`, else the team
8
+ standard `24.5.0`). Keep it identical to `.nvmrc` and the workflows.
9
+
10
+ ## `apps/web/Dockerfile`
11
+
12
+ ```dockerfile
13
+ FROM node:{{NODE_VERSION}}-alpine
14
+
15
+ # Set working directory
16
+ WORKDIR /app
17
+
18
+ # Copy package files and install production dependencies
19
+ COPY package.json package-lock.json ./
20
+ RUN npm ci --omit=dev --ignore-scripts
21
+
22
+ # Copy the built application from dist folder (already built in CI)
23
+ COPY dist/apps/web ./
24
+
25
+ # Build argument for version
26
+ ARG VERSION
27
+ ENV VERSION=$VERSION
28
+
29
+ # Set environment variables
30
+ ENV NODE_ENV=production
31
+ ENV PORT=80
32
+
33
+ # Expose the application port
34
+ EXPOSE 80
35
+
36
+ # Start the SSR server
37
+ CMD ["node", "server/server.mjs"]
38
+ ```
39
+
40
+ ## `.dockerignore` (recommended — the reference repo has none)
41
+
42
+ The `development.yml` build context is the whole repo (`docker buildx build ... .`). The Dockerfile
43
+ only `COPY`s `package*.json` and `dist/apps/web`, so a `.dockerignore` doesn't change the image, but
44
+ it keeps the build context small/fast. Add it unless the team objects:
45
+
46
+ ```gitignore
47
+ node_modules
48
+ .git
49
+ .github
50
+ .nx
51
+ .angular
52
+ coverage
53
+ dist/.playwright
54
+ **/*.spec.ts
55
+ *.log
56
+ ```
57
+
58
+ Note: do **not** ignore `dist/` — the build context must include `dist/apps/web`.