methodology-m 0.3.1

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 (38) hide show
  1. package/bin/m.mjs +76 -0
  2. package/dist-m/CHANGELOG.md +45 -0
  3. package/dist-m/capabilities/bootstrap-root-repo/SKILL.md +138 -0
  4. package/dist-m/capabilities/decompose-story/SKILL.md +299 -0
  5. package/dist-m/capabilities/generate-acceptance-tests/SKILL.md +305 -0
  6. package/dist-m/capabilities/generate-pats/SKILL.md +131 -0
  7. package/dist-m/capabilities/scaffold-repo/SKILL.md +641 -0
  8. package/dist-m/capabilities/setup-workspace/SKILL.md +70 -0
  9. package/dist-m/capabilities/tag-release/SKILL.md +121 -0
  10. package/dist-m/capabilities/wire-orchestration/SKILL.md +351 -0
  11. package/dist-m/m.md +126 -0
  12. package/dist-m/providers/provider-interface.md +191 -0
  13. package/dist-m/providers/scm/gitlab.md +377 -0
  14. package/dist-m/schemas/pat.schema.json +161 -0
  15. package/dist-m/schemas/project.schema.json +177 -0
  16. package/package.json +27 -0
  17. package/src/commands/changelog.mjs +58 -0
  18. package/src/commands/clone.mjs +199 -0
  19. package/src/commands/diff.mjs +29 -0
  20. package/src/commands/init.mjs +51 -0
  21. package/src/commands/update.mjs +41 -0
  22. package/src/commands/version.mjs +43 -0
  23. package/src/lib/copy.mjs +20 -0
  24. package/src/lib/detect-agent.mjs +25 -0
  25. package/src/lib/diff-trees.mjs +95 -0
  26. package/src/lib/topology.mjs +62 -0
  27. package/src/lib/version-file.mjs +25 -0
  28. package/src/lib/workspace.mjs +40 -0
  29. package/src/lib/wrappers/claude.mjs +54 -0
  30. package/templates/claude/skills/bootstrap-root-repo/SKILL.md +13 -0
  31. package/templates/claude/skills/decompose-story/SKILL.md +13 -0
  32. package/templates/claude/skills/generate-acceptance-tests/SKILL.md +13 -0
  33. package/templates/claude/skills/generate-pats/SKILL.md +13 -0
  34. package/templates/claude/skills/scaffold-repo/SKILL.md +13 -0
  35. package/templates/claude/skills/setup-workspace/SKILL.md +13 -0
  36. package/templates/claude/skills/tag-release/SKILL.md +13 -0
  37. package/templates/claude/skills/wire-orchestration/SKILL.md +13 -0
  38. package/templates/claude/steering/m-steering.md +3 -0
@@ -0,0 +1,641 @@
1
+ # scaffold-repo
2
+
3
+ **Capability:** Create and configure a managed repo for an M-type project
4
+
5
+ ## What it does
6
+
7
+ Creates a managed repo on GitLab from a sub-task file, seeds it with
8
+ project files, and installs a pluggable CI pipeline. After this step,
9
+ the repo is ready to receive implementation work — a dev can create a
10
+ branch, raise an MR, and the pipeline will build, test, publish a
11
+ snapshot artefact, and auto-tag on merge.
12
+
13
+ ## Parameters
14
+
15
+ | Parameter | Type | Required | Description |
16
+ |----------------|--------|----------|-----------------------------------------------------|
17
+ | sub-task-file | path | Yes | Path to the sub-task markdown file |
18
+ | project-yaml | path | Yes | Path to project.yaml (for group path, project name) |
19
+ | repo-type | string | No | "node", "python", "rust", etc. (default: node) |
20
+ | namespace-id | string | No | GitLab namespace ID (extracted from project if omitted) |
21
+
22
+ The sub-task file is the primary input. It contains the component name,
23
+ repo name, parent story reference, and repo-level PAT stubs. The
24
+ project.yaml provides the group path and project-level context.
25
+
26
+ ## Execution
27
+
28
+ ### Step 1 — Extract metadata
29
+
30
+ Read the sub-task file. Extract:
31
+ - Component name (from `Component:` field)
32
+ - Repo name (from `Repo:` field)
33
+ - Parent story ID (from `Parent:` field)
34
+ - Repo-level PAT stubs (from `## PAT Stubs` section)
35
+
36
+ Read project.yaml. Extract:
37
+ - Group path (from `group:` field)
38
+ - Project name (from `project:` field)
39
+
40
+ ### Step 2 — Create the repo
41
+
42
+ ```
43
+ scm.create_repo(
44
+ name: <repo-name>,
45
+ namespace_id: <resolved-from-group-path>,
46
+ initialize_readme: false
47
+ )
48
+ ```
49
+
50
+ Do NOT initialise with README — the seed commit includes a project-specific README.
51
+
52
+ ### Step 3 — Seed project files
53
+
54
+ Push all seed files in a single commit:
55
+
56
+ ```
57
+ scm.push_files(repo: <repo>, branch: "main", files: [...], commit_message: "seed: scaffold <component>")
58
+ ```
59
+
60
+ - `README.md` — component name, parent story reference, repo-level PATs
61
+ - CI config — pluggable lifecycle pipeline (see Pipeline Template)
62
+ - `.gitignore` — standard ignores for the repo type (e.g. `node_modules/`)
63
+ - `package.json` (or equivalent for repo-type) — with lifecycle scripts
64
+ - `package-lock.json` — minimal lockfile so `npm ci` works from day zero
65
+ - `jira/<sub-task-id>.md` — copy of the sub-task file
66
+ - `pats/<sub-task-id>.stub` — PAT stub file extracted from the sub-task
67
+ - `.m/steering/m-managed-repo.md` — M development guide (see Steering Template)
68
+
69
+ **For frontend repos with API dependencies (role: frontend, frontend-host):**
70
+
71
+ - `pats/stubs/<api-name>.js` — API stub servers for PAT validation (see API Stubs below)
72
+
73
+ The webpack config and Dockerfile must declare one env var per API
74
+ dependency, derived from the topology in project.yaml:
75
+
76
+ | Dependency role | Env var | Default |
77
+ |----------------|---------|---------|
78
+ | api-read | `API_READ_URL` | `http://localhost:3002` |
79
+ | api-write | `API_WRITE_URL` | `http://localhost:3003` |
80
+ | (general pattern) | `<ROLE_UPPER>_URL` | `http://localhost:<port>` |
81
+
82
+ Ports follow the convention: shell=3000, MFE=3001, then backends in
83
+ project.yaml order starting at 3002.
84
+
85
+ **Important:** Because the repo was created without a default README
86
+ (Step 2), all files are new and `push_files` works cleanly. If any files
87
+ already exist (e.g. re-running scaffold on a partially seeded repo), use
88
+ `scm.create_or_update_file()` per file instead.
89
+
90
+ ### Step 4 — Reconfigure branch protection
91
+
92
+ The methodology requires that `main` is merge-only — no direct pushes.
93
+
94
+ ```
95
+ scm.protect_branch(
96
+ repo: <repo>,
97
+ branch: "main",
98
+ push: none,
99
+ merge: maintainer,
100
+ force_push: false
101
+ )
102
+ ```
103
+
104
+ **Ordering matters:** Step 3 (seed files) must complete before Step 4
105
+ (protect branch). Once push is blocked, even the automation token
106
+ cannot push directly — only MR merges work.
107
+
108
+ ### Step 5 — Create access token for merge transaction
109
+
110
+ ```
111
+ scm.create_access_token(
112
+ repo: <repo>,
113
+ name: "m-merge-transaction",
114
+ scopes: [api, read_repo, write_repo],
115
+ access_level: maintainer,
116
+ expiry: 1 year
117
+ )
118
+ ```
119
+
120
+ This token is used by the root repo's merge transaction pipeline to
121
+ merge MRs on this managed repo. Store the token value — it will be
122
+ passed to `wire-orchestration` for installation as a root repo CI secret.
123
+
124
+ ### Step 6 — Report
125
+
126
+ Output:
127
+ - Repo URL
128
+ - Branch protection status (push: no one, merge: maintainers)
129
+ - Access token status (project token created / free tier — use group PAT)
130
+ - Summary of seeded files
131
+
132
+ Note that `wire-orchestration` should be run after all managed repos
133
+ are scaffolded to connect them to the root repo.
134
+
135
+ ## Pipeline Template
136
+
137
+ The `.gitlab-ci.yml` uses a pluggable lifecycle model. The pipeline
138
+ defines WHEN things run. The project's build scripts define WHAT runs.
139
+
140
+ ```
141
+ # .gitlab-ci.yml — M-type managed repo pipeline
142
+ # Lifecycle phases are delegated to project scripts.
143
+ # The pipeline orchestrates; the scripts implement.
144
+
145
+ image: node:20
146
+
147
+ # Workflow rules prevent duplicate pipelines (one for branch push,
148
+ # one for MR) and ensure all jobs — including those with per-job
149
+ # rules — participate correctly in MR pipelines.
150
+ workflow:
151
+ rules:
152
+ - if: $CI_MERGE_REQUEST_IID # MR pipelines
153
+ - if: $CI_COMMIT_BRANCH == "main" # post-merge on main
154
+
155
+ stages:
156
+ - install
157
+ - build
158
+ - test
159
+ - snapshot
160
+ - tag
161
+
162
+ # Default cache: every job pulls node_modules from cache.
163
+ # Only the install job pushes (pull-push policy).
164
+ cache: &default_cache
165
+ key: ${CI_COMMIT_REF_SLUG}
166
+ paths:
167
+ - node_modules/
168
+ policy: pull
169
+
170
+ # --- Always run ---
171
+
172
+ install:
173
+ stage: install
174
+ script:
175
+ - npm ci
176
+ cache:
177
+ <<: *default_cache
178
+ policy: pull-push
179
+
180
+ build:
181
+ stage: build
182
+ script:
183
+ - npm run build --if-present
184
+ needs: [install]
185
+
186
+ test:
187
+ stage: test
188
+ script:
189
+ - npm test
190
+ needs: [build]
191
+
192
+ # --- MR pipelines only: publish snapshot artefact ---
193
+
194
+ snapshot:
195
+ stage: snapshot
196
+ script:
197
+ - npm run snapshot --if-present
198
+ rules:
199
+ - if: $CI_MERGE_REQUEST_IID
200
+ needs: [test]
201
+
202
+ # --- Merge to main only: auto-tag ---
203
+
204
+ tag:
205
+ stage: tag
206
+ script:
207
+ - npm run tag --if-present
208
+ rules:
209
+ - if: $CI_COMMIT_BRANCH == "main"
210
+ needs: [test]
211
+ ```
212
+
213
+ ### Lifecycle scripts (package.json)
214
+
215
+ The scaffold creates placeholder scripts. The implementation step
216
+ replaces them with real commands.
217
+
218
+ **Backend repos (role: backend):**
219
+ ```
220
+ {
221
+ "scripts": {
222
+ "start": "node src/server.js",
223
+ "build": "echo 'no build step configured'",
224
+ "test": "echo 'no tests configured' && exit 0",
225
+ "snapshot": "echo 'snapshot: not yet implemented'",
226
+ "tag": "echo 'auto-tag: not yet implemented'"
227
+ }
228
+ }
229
+ ```
230
+
231
+ **Frontend repos (role: frontend, frontend-host):**
232
+ ```
233
+ {
234
+ "scripts": {
235
+ "start": "webpack serve --mode development",
236
+ "build": "webpack --mode production",
237
+ "test": "echo 'no tests configured' && exit 0",
238
+ "snapshot": "echo 'snapshot: not yet implemented'",
239
+ "tag": "echo 'auto-tag: not yet implemented'"
240
+ }
241
+ }
242
+ ```
243
+
244
+ The `start` script is required for local compose — the root repo's
245
+ `start-all.sh` calls each component's start script. Backend repos
246
+ get a Node server start; frontend repos get webpack dev server.
247
+ Implementation may refine these but the scaffold must provide
248
+ working defaults.
249
+
250
+ ### Lifecycle phase contract
251
+
252
+ | Phase | Trigger | Purpose | Failure behaviour |
253
+ |------------|--------------------------|--------------------------------------|--------------------------|
254
+ | `install` | Every pipeline | Install dependencies | Pipeline fails |
255
+ | `build` | Every pipeline | Compile, bundle, transpile | Pipeline fails |
256
+ | `test` | Every pipeline | Run repo-level PATs and unit tests | Pipeline fails, MR blocked |
257
+ | `snapshot` | MR pipelines only | Publish pre-release artefact | MR can't shadow-integrate |
258
+ | `tag` | Merge to main only | Create semver tag on the new commit | Manual tag required |
259
+
260
+ **CI image:** The pipeline must specify `image: node:20` (or the
261
+ appropriate runtime for the repo-type). Without an explicit image,
262
+ GitLab falls back to the runner's default (typically `ruby:3.1`),
263
+ which won't have npm/node available.
264
+
265
+ The `--if-present` flag on npm run means phases without a script
266
+ silently succeed. This lets the scaffold work out of the box before
267
+ implementation fills in the real scripts.
268
+
269
+ ### Snapshot artefact convention
270
+
271
+ On MR pipelines, the `snapshot` phase publishes a pre-release artefact
272
+ that the root repo's shadow integration can reference. The convention:
273
+
274
+ - Git tag: `v0.0.0-mr.<MR_IID>` on the MR branch head commit
275
+ - The root repo's topology MR pins to this tag
276
+ - The tag is lightweight (not annotated) — it's ephemeral
277
+
278
+ This works on GitLab free tier without a package registry. The root
279
+ repo clones the managed repo at the snapshot tag to compose the system.
280
+
281
+ ### Auto-tag convention
282
+
283
+ On merge to main, the `tag` phase creates an annotated semver tag.
284
+ Version bumping strategy is determined by the project — the scaffold
285
+ provides the hook, the implementation decides the logic.
286
+
287
+ ## API Stubs for Frontend Repos
288
+
289
+ When scaffolding a frontend component (role: frontend or frontend-host)
290
+ that depends on API components, generate stub servers in `pats/stubs/`.
291
+ These stubs enable PAT validation without running real API services.
292
+
293
+ ### Generation rules
294
+
295
+ 1. Read the API sub-task files to extract the endpoint contracts
296
+ (routes, methods, request/response shapes)
297
+ 2. Generate one stub file per API dependency in `pats/stubs/`
298
+ 3. Stubs use Express with CORS enabled
299
+ 4. Read stubs return mock data matching the contract
300
+ 5. Write stubs accept and store data in memory
301
+ 6. Read and write stubs sharing the same story must share state
302
+ (same in-memory array, via require/import)
303
+
304
+ ### Shared state pattern
305
+
306
+ When a story involves both a read and write API, the stubs must share
307
+ an in-memory data store so that writes are visible to subsequent reads.
308
+ Pattern:
309
+
310
+ - `pats/stubs/api-read.js` — exports the shared data array, starts
311
+ the read server as a side effect
312
+ - `pats/stubs/api-write.js` — requires api-read to get the shared
313
+ array reference, starts the write server
314
+
315
+ Running `node pats/stubs/api-write.js` starts both servers (api-read
316
+ is loaded via require, which triggers its listen call).
317
+
318
+ ### Stub template (read API)
319
+
320
+ ```
321
+ const express = require('express')
322
+ const cors = require('cors')
323
+
324
+ const app = express()
325
+ app.use(cors())
326
+
327
+ const <collection> = []
328
+
329
+ app.get('/<resource>', (req, res) => {
330
+ res.json(<collection>)
331
+ })
332
+
333
+ app.get('/health', (req, res) => {
334
+ res.json({ status: 'ok' })
335
+ })
336
+
337
+ const PORT = process.env.STUB_PORT || <read-port>
338
+ const server = app.listen(PORT, () => {
339
+ console.log('<api-name> stub on port ' + PORT)
340
+ })
341
+
342
+ module.exports = { app, <collection>, server }
343
+ ```
344
+
345
+ ### Stub template (write API)
346
+
347
+ ```
348
+ const express = require('express')
349
+ const cors = require('cors')
350
+ const { <collection> } = require('./api-read')
351
+
352
+ const app = express()
353
+ app.use(cors())
354
+ app.use(express.json())
355
+
356
+ let nextId = 1
357
+
358
+ app.post('/<resource>', (req, res) => {
359
+ const { <field> } = req.body || {}
360
+ if (!<field> || !<field>.trim()) {
361
+ return res.status(400).json({ error: '<Field> is required' })
362
+ }
363
+ const item = { id: nextId++, <field>: <field>.trim() }
364
+ <collection>.push(item)
365
+ res.status(201).json(item)
366
+ })
367
+
368
+ app.get('/health', (req, res) => {
369
+ res.json({ status: 'ok' })
370
+ })
371
+
372
+ const PORT = process.env.STUB_WRITE_PORT || <write-port>
373
+ app.listen(PORT, () => {
374
+ console.log('<api-name> stub on port ' + PORT)
375
+ })
376
+ ```
377
+
378
+ ### Dependencies
379
+
380
+ Add `express` and `cors` as devDependencies in the frontend repo's
381
+ `package.json` if not already present (they're needed for stubs only,
382
+ not for the frontend build).
383
+
384
+ ## Notes
385
+
386
+ - The pipeline template shown is for `repo-type: node`. Other repo
387
+ types follow the same lifecycle phases but use different package
388
+ managers (pip, cargo, etc.)
389
+ - The pipeline is intentionally minimal — no Docker, no registry, no
390
+ deployment. Those are concerns for later stages.
391
+ - Branch protection requires an unprotect/re-protect sequence because
392
+ GitLab auto-protects `main` on repo creation and there is no API to
393
+ update existing protection settings.
394
+ - On free tier, project access tokens are unavailable. Use a group-level
395
+ personal access token as fallback. Document this in the report step.
396
+ - When re-running scaffold on a partially seeded repo, use
397
+ `create_or_update_file` instead of `push_files` for any files that
398
+ may already exist. `push_files` rejects commits with existing files.
399
+ - `scaffold-repo` does NOT set up webhooks or root repo CI — that's
400
+ `wire-orchestration`.
401
+ - Always include a `package-lock.json` in the seed commit (even with no
402
+ dependencies). Without it, `npm ci` fails immediately. The lockfile
403
+ is trivial for an empty project — just name, version, lockfileVersion.
404
+ - **CI cache with DAG (`needs:`):** GitLab `needs:` creates a DAG but
405
+ does NOT propagate cache between jobs. Each job that requires
406
+ `node_modules/` must declare its own cache block. The template uses a
407
+ YAML anchor (`&default_cache`) with `policy: pull` as the default, and
408
+ the install job overrides to `policy: pull-push`. Without this, jobs
409
+ downstream of install (build, test, etc.) won't find `node_modules/`
410
+ and commands like `vitest` will fail with "not found".
411
+ - **CORS for backend repos:** When `role: backend`, the seed `app.js`
412
+ must include `cors` middleware (`app.use(cors())`) and `cors` must be
413
+ listed as a dependency in `package.json`. Without this, any frontend
414
+ on a different port will fail to fetch from the API — the default
415
+ development scenario for distributed topologies. CORS is permissive
416
+ by default for development; production lockdown is a deployment concern.
417
+ - **Port convention:** Backend APIs must use port defaults that don't
418
+ collide with frontend dev servers. The convention is deterministic
419
+ based on component order in `project.yaml`:
420
+ - shell (frontend-host): 3000
421
+ - first frontend: 3001
422
+ - first backend: 3002
423
+ - second backend: 3003
424
+ - ...and so on
425
+ The port is set via `PORT` env var with the conventional default in
426
+ `server.js`. Frontend components referencing APIs must use the correct
427
+ port in their `API_URL` default.
428
+
429
+ ## Steering Template
430
+
431
+ The `.m/steering/m-managed-repo.md` file gives any AI agent the context
432
+ needed to work in an M-managed repo. It is generated from the sub-task
433
+ metadata and project configuration, and placed in the repo's `.m/steering/`
434
+ directory. Agent-specific adapters can symlink or include it from their
435
+ own steering locations (e.g. `.kiro/steering/`, `.claude/steering/`).
436
+
437
+ ### Template
438
+
439
+ The following is parameterised. Replace `<placeholders>` with values
440
+ extracted from the sub-task file and project.yaml.
441
+
442
+ ```
443
+ ---
444
+ inclusion: auto
445
+ ---
446
+
447
+ # M-Managed Repo — AI Development Guide
448
+
449
+ This repo is managed by Methodology M. This steering file gives you the
450
+ context needed to work effectively in this codebase.
451
+
452
+ ## Repo Identity
453
+
454
+ - **Component:** <component-name>
455
+ - **Role:** <component-role> (e.g. backend, frontend, frontend-host)
456
+ - **Type:** referenced (tracked by version in the root repo)
457
+ - **Root repo:** <root-repo-name> (under the same GitLab group)
458
+ - **Story management:** Sub-task files in `jira/`, linked to parent stories in the root repo
459
+
460
+ ## Methodology M Essentials
461
+
462
+ This is an M-type managed repo. Key concepts:
463
+
464
+ - **PATs (Pseudo Acceptance Tests)** are structured, machine-readable
465
+ validation criteria. They describe what to verify without prescribing
466
+ a test framework. PAT stubs in `pats/` are pseudocode — human-readable
467
+ intent that must be transformed into executable acceptance tests.
468
+
469
+ - **Repo-level PATs** answer: "does this component fulfil its contract?"
470
+ They run in this repo's CI pipeline and test the component in isolation.
471
+
472
+ - **Story-level PATs** live in the root repo and test the composed system.
473
+ This repo doesn't run those — it only owns its own contract.
474
+
475
+ - **Acceptance tests (ATs)** are the executable form of PATs. The PAT stub
476
+ is the specification; the AT is the compiled, CI-runnable proof. By the
477
+ time an MR is raised, every PAT must have a corresponding passing AT.
478
+
479
+ ## Artefact Layout
480
+
481
+ jira/ Sub-task files (acceptance criteria, parent story link)
482
+ pats/ PAT stubs (.stub.js) and acceptance tests (.spec.js)
483
+ src/ Application source code
484
+ .gitlab-ci.yml CI pipeline (lifecycle phases, not hardcoded commands)
485
+ package.json Lifecycle scripts (build, test, start, snapshot, tag)
486
+
487
+ ## Sub-Task Files
488
+
489
+ Files in `jira/` describe what this repo must deliver for a given story.
490
+ Each sub-task has:
491
+
492
+ - A parent story reference
493
+ - Acceptance criteria (repo-level PATs in prose)
494
+ - A summary of the contract this component owns
495
+
496
+ When asked to implement something, read the sub-task file first. It is the
497
+ contract. Build exactly what it specifies.
498
+
499
+ ## PAT Stubs and Acceptance Tests
500
+
501
+ Files in `pats/` come in two forms:
502
+
503
+ - `*.stub.js` — PAT stubs. Pseudocode describing what to test. These are
504
+ generated during story decomposition and represent the contract in
505
+ human-readable form. Do not delete them after transformation.
506
+
507
+ - `*.spec.js` — Acceptance tests. Real, executable test code that proves
508
+ the implementation meets the contract. Generated by transforming stubs.
509
+
510
+ ### Transforming PAT Stubs into Acceptance Tests
511
+
512
+ When asked to "generate acceptance tests" or "transform PAT stubs":
513
+
514
+ 1. Read the stub file to understand the contract
515
+ 2. Read the sub-task file for additional context
516
+ 3. Check `package.json` `"test"` script and any test config files
517
+ (e.g. `cypress.config.js`, `vitest.config.js`) to determine the
518
+ repo's acceptance test framework. Do NOT guess from the component
519
+ role — use what the repo is actually configured to run.
520
+ 4. **PATs are user-level acceptance criteria.** For frontend components,
521
+ PAT→CAT compilation MUST produce browser-based tests (Cypress,
522
+ Playwright, etc.) that verify behaviour through the real UI — not
523
+ vitest/jsdom unit tests. Unit tests are optional developer-level
524
+ tests, not PAT compilations.
525
+ - Frontend (any role) → Cypress or configured browser test framework
526
+ - Backend API → supertest + vitest (HTTP contract testing against
527
+ the real app, not mocked)
528
+ 5. Write the spec file alongside the stub (same directory, matching the
529
+ configured spec pattern — e.g. *.cy.js for Cypress)
530
+
531
+ ## API Stubs and CI Coupling
532
+
533
+ API stubs in `pats/stubs/` enable testing without real API services.
534
+ They run both locally (for PAT validation) and in CI (for acceptance tests).
535
+
536
+ **CRITICAL — stubs and CI are coupled:**
537
+
538
+ - Every stub file in `pats/stubs/api-*.js` MUST be started in the CI
539
+ test job. If you add or modify a stub, you MUST update `.gitlab-ci.yml`
540
+ to start it and wait on its health endpoint.
541
+ - Every stub MUST expose a `GET /health` endpoint so CI can `wait-on` it.
542
+ - Stubs sharing the same resource (e.g. read + write on todos) MUST share
543
+ state via `pats/stubs/shared-state.js`. Stateless stubs cannot validate
544
+ write→read round-trips.
545
+ - Before raising an MR, verify that `.gitlab-ci.yml` test job starts ALL
546
+ stubs in `pats/stubs/` and waits on ALL their health endpoints.
547
+
548
+ ## CI Pipeline
549
+
550
+ The .gitlab-ci.yml uses lifecycle phases delegated to package.json scripts:
551
+
552
+ | Phase | Script | When |
553
+ |-------|--------|------|
554
+ | start | npm start | Local development (compose) |
555
+ | install | npm ci | Every pipeline |
556
+ | build | npm run build | Every pipeline |
557
+ | test | npm test | Every pipeline |
558
+ | snapshot | npm run snapshot | MR pipelines only |
559
+ | tag | npm run tag | Merge to main only |
560
+
561
+ The pipeline orchestrates; the scripts implement. When updating functionality,
562
+ update the package.json scripts — not the CI file.
563
+
564
+ ## Branch and MR Conventions
565
+
566
+ - Feature branches: feat/<subtask-id>-<description>
567
+ - MR titles must include the sub-task ID
568
+ - Branch protection: push to main is blocked; all changes go through MRs
569
+ - Managed repo MRs stay open until the root repo's merge transaction lands
570
+ the full story — do not merge individually
571
+
572
+ ## Development Workflow
573
+
574
+ The typical cycle for implementing a sub-task:
575
+
576
+ 1. Read the sub-task file in jira/ — understand the contract
577
+ 2. Create a feature branch from main
578
+ 3. Implement towards the PATs — this is a continuous validation loop:
579
+ - Write code that addresses the acceptance criteria
580
+ - **UX fidelity rule:** if the sub-task includes a UX Reference section
581
+ with screenshots or mockups, the implementation must visually match
582
+ the design — layout, colours, shadows, rounded corners, spacing,
583
+ typography weight. Not pixel-perfect, but recognisably the same
584
+ design. Use the screenshots as your visual target. When no UX
585
+ reference exists, minimal functional styling is acceptable.
586
+ - Validate against PATs as you go using appropriate tools:
587
+ - **Frontend:** open in browser, verify visually, check data-testid
588
+ attributes, test interactions (use Chrome DevTools MCP if available)
589
+ - **API:** curl the endpoints, verify responses match the contract
590
+ - Use API stubs in `pats/stubs/` to test against dependency contracts
591
+ without needing real services running
592
+ - **Shared-state rule:** if the sub-task involves both reading and
593
+ writing the same resource (e.g. GET /todos and POST /todos), the
594
+ stubs MUST share state. A write through the write stub must be
595
+ visible to a subsequent read through the read stub. Stateless stubs
596
+ that return hardcoded data CANNOT validate write→read round-trips.
597
+ Check `pats/stubs/` for a shared-state module — if one doesn't
598
+ exist and the story requires it, create one before validating.
599
+ - **Round-trip validation rule:** any acceptance criterion that says
600
+ "new item appears in the list" or "data persists after action"
601
+ MUST be validated end-to-end: perform the write action in the UI,
602
+ then verify the result appears via the read path. If the item
603
+ doesn't appear, the PAT is NOT satisfied — do not hand-wave with
604
+ "the stub is stateless." Fix the stubs.
605
+ - A passing build is NOT PAT validation — you must demonstrate that
606
+ the implementation satisfies the acceptance criteria
607
+ - Never declare implementation complete without this demonstration
608
+ 4. Compile PAT stubs into acceptance tests (CATs) using the repo's
609
+ configured test framework (check package.json and test config files)
610
+ 5. Run npm test locally — all acceptance tests must pass
611
+ 6. **Verify CI readiness:** check `.gitlab-ci.yml` test job starts all
612
+ stubs in `pats/stubs/` and waits on all health endpoints. If you
613
+ added or changed stubs, update the CI config.
614
+ 7. Commit, push, open MR with sub-task ID in the title
615
+ 8. CI runs: install → build → test → snapshot
616
+ 9. Wait for story-level integration (managed by root repo)
617
+
618
+ ## Code Conventions
619
+
620
+ - Separate app logic from server binding (app.js / server.js pattern)
621
+ so tests can import the app without starting a server
622
+ - Keep implementations minimal — deliver exactly what the sub-task specifies
623
+ - No framework-specific magic — keep it readable and testable
624
+ - Backend APIs must include CORS middleware (`app.use(cors())`) — required
625
+ for cross-origin requests from frontends in development
626
+ - Use `PORT` env var for server binding with a conventional default that
627
+ avoids collisions (shell=3000, MFE=3001, api-read=3002, api-write=3003)
628
+ ```
629
+
630
+ ### Parameterisation
631
+
632
+ The scaffold capability fills in these placeholders from the inputs:
633
+
634
+ | Placeholder | Source |
635
+ |-------------|--------|
636
+ | `<component-name>` | Sub-task file `Component:` field |
637
+ | `<component-role>` | Inferred from component type (API → backend, MFE → frontend, shell → frontend-host) |
638
+ | `<root-repo-name>` | project.yaml `project:` field + `-root` suffix |
639
+
640
+ Everything else in the template is generic M methodology content that
641
+ applies to all managed repos regardless of project.