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,191 @@
1
+ # Provider Interface
2
+
3
+ This document defines the function namespaces that M capabilities call,
4
+ and the protocol for resolving those calls to concrete implementations.
5
+
6
+ ## Resolution Protocol
7
+
8
+ 1. Capability calls a namespaced function, e.g. `scm.create_repo(...)`
9
+ 2. The namespace prefix (`scm`) identifies the provider category
10
+ 3. `project.yaml` declares which provider is active per category:
11
+ ```yaml
12
+ providers:
13
+ scm: gitlab
14
+ ```
15
+ 4. The agent reads `.m/providers/<category>/<provider>.md` to find the
16
+ function's concrete implementation (API calls, MCP tools, CLI commands)
17
+ 5. The agent executes the concrete implementation
18
+
19
+ Config resolution for the provider choice itself follows the standard
20
+ M hierarchy:
21
+
22
+ ```
23
+ Repo-level override → Project-level (project.yaml) → Org Config → M Core Config
24
+ ```
25
+
26
+ ## Namespaces
27
+
28
+ ### `scm` — Source Code Management
29
+
30
+ Repo and project lifecycle, branch protection, webhooks, CI secrets,
31
+ and commit status reporting.
32
+
33
+ **Active providers:** `gitlab` (reference implementation)
34
+
35
+ #### Functions
36
+
37
+ | Function | Parameters | Returns | Used by |
38
+ |---|---|---|---|
39
+ | `scm.create_group` | `name`, `path`, `visibility`, `description`, `parent_id?` | group ID | setup-workspace |
40
+ | `scm.resolve_group_id` | `group_path` | group ID | setup-workspace |
41
+ | `scm.resolve_project_id` | `project_path` | project ID | wire-orchestration |
42
+ | `scm.create_repo` | `name`, `namespace_id`, `initialize_readme` | repo URL, project ID | bootstrap-root-repo, scaffold-repo |
43
+ | `scm.push_files` | `repo`, `branch`, `files[]`, `commit_message` | commit SHA | bootstrap-root-repo, scaffold-repo |
44
+ | `scm.create_or_update_file` | `repo`, `path`, `content`, `commit_message`, `branch` | commit SHA | scaffold-repo (re-run) |
45
+ | `scm.protect_branch` | `repo`, `branch`, `push`, `merge`, `force_push` | — | scaffold-repo, wire-orchestration |
46
+ | `scm.create_access_token` | `repo`, `name`, `scopes[]`, `access_level`, `expiry` | token value | scaffold-repo |
47
+ | `scm.create_pipeline_trigger` | `repo`, `description` | trigger token | wire-orchestration |
48
+ | `scm.create_webhook` | `repo`, `url`, `events{}`, `ssl_verify` | webhook ID | wire-orchestration |
49
+ | `scm.store_ci_secret` | `repo`, `key`, `value`, `protected`, `masked` | — | wire-orchestration |
50
+ | `scm.post_commit_status` | `repo`, `sha`, `state`, `name`, `description`, `target_url` | — | wire-orchestration (CI scripts) |
51
+ | `scm.create_branch` | `repo`, `branch`, `ref` | — | decompose-story |
52
+ | `scm.create_merge_request` | `repo`, `source_branch`, `target_branch`, `title`, `description?` | MR URL | decompose-story |
53
+
54
+ #### Function Contracts
55
+
56
+ **`scm.create_group(name, path, visibility, description, parent_id?)`**
57
+
58
+ Create a group/org on the SCM platform. If `parent_id` is provided,
59
+ creates a subgroup. Returns the group ID.
60
+
61
+ ---
62
+
63
+ **`scm.resolve_group_id(group_path)`**
64
+
65
+ Resolve a human-readable group path (e.g. `methodology-m`) to the
66
+ platform's internal group ID. Needed when other functions require
67
+ numeric IDs.
68
+
69
+ ---
70
+
71
+ **`scm.resolve_project_id(project_path)`**
72
+
73
+ Resolve a project path (e.g. `methodology-m/todo-m-workshop/todo-m-api-read`)
74
+ to the platform's internal project ID.
75
+
76
+ ---
77
+
78
+ **`scm.create_repo(name, namespace_id, initialize_readme)`**
79
+
80
+ Create a new repository. `initialize_readme` MUST default to `false` —
81
+ M capabilities seed repos with their own initial commit. A platform
82
+ default README causes merge conflicts with the seed.
83
+
84
+ ---
85
+
86
+ **`scm.push_files(repo, branch, files[], commit_message)`**
87
+
88
+ Push multiple files in a single atomic commit. Each file entry contains
89
+ `path` and `content`. Fails if any file already exists on the branch —
90
+ use `scm.create_or_update_file()` for idempotent writes.
91
+
92
+ ---
93
+
94
+ **`scm.create_or_update_file(repo, path, content, commit_message, branch)`**
95
+
96
+ Create or overwrite a single file. Idempotent — safe for re-runs on
97
+ partially seeded repos.
98
+
99
+ ---
100
+
101
+ **`scm.protect_branch(repo, branch, push, merge, force_push)`**
102
+
103
+ Set branch protection rules. M requires:
104
+ - `push: none` — no direct pushes, all changes via MR/PR
105
+ - `merge: maintainer` — only maintainer-level can merge
106
+ - `force_push: false`
107
+
108
+ The provider implementation must handle platform quirks (e.g. removing
109
+ existing protection before applying new rules).
110
+
111
+ ---
112
+
113
+ **`scm.create_access_token(repo, name, scopes[], access_level, expiry)`**
114
+
115
+ Create a scoped token for automated operations on this repo. Used by
116
+ the merge transaction pipeline. Token value is returned once and must
117
+ be stored immediately via `scm.store_ci_secret()`.
118
+
119
+ Provider must document fallback options for platforms/tiers where
120
+ per-repo tokens are unavailable.
121
+
122
+ ---
123
+
124
+ **`scm.create_pipeline_trigger(repo, description)`**
125
+
126
+ Create a trigger mechanism that external webhooks can use to start
127
+ CI pipelines on this repo. Returns a token or URL.
128
+
129
+ ---
130
+
131
+ **`scm.create_webhook(repo, url, events{}, ssl_verify)`**
132
+
133
+ Install a webhook on a repo. `events` specifies which events fire
134
+ the webhook. For M, only MR/PR events should trigger — push events
135
+ MUST be explicitly disabled.
136
+
137
+ ---
138
+
139
+ **`scm.store_ci_secret(repo, key, value, protected, masked)`**
140
+
141
+ Store a secret as a CI environment variable on a repo. `protected`
142
+ means only available on protected branches. `masked` means hidden
143
+ in job logs.
144
+
145
+ ---
146
+
147
+ **`scm.post_commit_status(repo, sha, state, name, description, target_url)`**
148
+
149
+ Report a build/test status on a specific commit. Used by shadow
150
+ integration to push results back to managed repo MRs. `state` is
151
+ one of: `success`, `failed`, `pending`.
152
+
153
+ ---
154
+
155
+ **`scm.create_branch(repo, branch, ref)`**
156
+
157
+ Create a new branch from an existing ref (branch, tag, or SHA).
158
+ Used by `decompose-story` to create the root repo's story branch
159
+ for the integration gate.
160
+
161
+ ---
162
+
163
+ **`scm.create_merge_request(repo, source_branch, target_branch, title, description?)`**
164
+
165
+ Create a merge request / pull request. Returns the MR URL. Used by
166
+ `decompose-story` to raise the root repo MR that establishes the
167
+ integration gate from the moment the story is decomposed.
168
+
169
+ ---
170
+
171
+ ## Adding a New Namespace
172
+
173
+ When a new provider category is needed (e.g. `test.cat.*`):
174
+
175
+ 1. Add the namespace to this document with its function signatures
176
+ 2. Create a provider implementation at `.m/providers/<category>/<provider>.md`
177
+ 3. Add the category to `project.yaml` under `providers:`
178
+ 4. Update capabilities to use the new namespaced functions
179
+
180
+ ## Adding a New Provider
181
+
182
+ To implement an existing namespace for a new platform (e.g. `scm/github`):
183
+
184
+ 1. Create `.m/providers/<category>/<provider>.md`
185
+ 2. Implement every function listed in the namespace's contract above
186
+ 3. Document all platform-specific gotchas in the provider file
187
+ 4. The provider is selectable via `project.yaml`:
188
+ ```yaml
189
+ providers:
190
+ scm: github
191
+ ```
@@ -0,0 +1,377 @@
1
+ # SCM Provider: GitLab
2
+
3
+ Implementation of the `scm.*` functions for GitLab. This is the reference
4
+ provider — the one used by the reference implementation (todo-m-workshop).
5
+
6
+ When executing M capabilities against GitLab, read this file to understand
7
+ how each `scm.*` function maps to GitLab API calls and MCP tools.
8
+
9
+ ## MCP tools
10
+
11
+ This provider uses two GitLab MCP servers:
12
+ - `gitlab_ops` — project/group management, branch protection, webhooks, tokens
13
+ - `gitlab` — file operations, commits, MR management
14
+
15
+ ---
16
+
17
+ ## scm.create_group
18
+
19
+ Create a GitLab group or subgroup.
20
+
21
+ ```
22
+ MCP: gitlab_ops
23
+ Tool: mcp_gitlab_ops_create_group
24
+
25
+ Parameters:
26
+ name: <name>
27
+ path: <path>
28
+ visibility: <visibility>
29
+ description: <description>
30
+ parent_id: <parent_id> # omit for top-level group
31
+ ```
32
+
33
+ **Gotchas:**
34
+ - **GitLab.com SaaS:** Top-level group creation via API is disabled.
35
+ Create parent group manually via UI, then use `parent_id` for subgroup.
36
+ - **GitLab Self-Managed:** Both top-level and subgroup creation work via API.
37
+
38
+ ---
39
+
40
+ ## scm.resolve_group_id
41
+
42
+ Resolve a group path (e.g. `methodology-m`) to a numeric group ID.
43
+
44
+ ```
45
+ MCP: gitlab_ops
46
+ Tool: mcp_gitlab_ops_get_group
47
+
48
+ Parameters:
49
+ group_path: <path>
50
+
51
+ Returns: group.id
52
+ ```
53
+
54
+ ---
55
+
56
+ ## scm.resolve_project_id
57
+
58
+ Resolve a project path (e.g. `methodology-m/todo-m-workshop/todo-m-api-read`)
59
+ to a numeric project ID.
60
+
61
+ ```
62
+ MCP: gitlab_ops
63
+ Tool: mcp_gitlab_ops_get_project
64
+
65
+ Parameters:
66
+ project_path: <path>
67
+
68
+ Returns: project.id
69
+ ```
70
+
71
+ ---
72
+
73
+ ## scm.create_repo
74
+
75
+ Create a new GitLab project (repo) in a namespace.
76
+
77
+ ```
78
+ MCP: gitlab_ops
79
+ Tool: mcp_gitlab_ops_create_project
80
+
81
+ Parameters:
82
+ name: <name>
83
+ namespace_id: <namespace_id>
84
+ initialize_with_readme: false # CRITICAL — see note
85
+ ```
86
+
87
+ **Gotchas:**
88
+ - Do NOT set `initialize_with_readme: true`. The seed commit in the
89
+ capability includes a project-specific README. Initialising with
90
+ GitLab's default README causes a conflict when pushing seed files.
91
+
92
+ ---
93
+
94
+ ## scm.push_files
95
+
96
+ Push multiple files in a single commit to a repo.
97
+
98
+ ```
99
+ MCP: gitlab
100
+ Tool: mcp_gitlab_push_files
101
+
102
+ Parameters:
103
+ project_id: <project_id>
104
+ branch: <branch>
105
+ commit_message: <message>
106
+ files: [
107
+ { file_path: <path>, content: <content> },
108
+ ...
109
+ ]
110
+ ```
111
+
112
+ **Gotchas:**
113
+ - `push_files` rejects commits that touch files already existing on
114
+ the branch. For re-runs on partially seeded repos, use
115
+ `scm.create_or_update_file()` per file instead.
116
+
117
+ ---
118
+
119
+ ## scm.create_or_update_file
120
+
121
+ Create or update a single file in a repo.
122
+
123
+ ```
124
+ MCP: gitlab
125
+ Tool: mcp_gitlab_create_or_update_file
126
+
127
+ Parameters:
128
+ project_id: <project_id>
129
+ file_path: <path>
130
+ content: <content>
131
+ commit_message: <message>
132
+ branch: <branch>
133
+ ```
134
+
135
+ ---
136
+
137
+ ## scm.protect_branch
138
+
139
+ Set branch protection to merge-only (no direct push).
140
+
141
+ GitLab auto-protects `main` on repo creation with `push_access_level: 40`
142
+ (maintainers can push). There is **no update API** for existing protection.
143
+ The sequence is: unprotect first, then re-protect with correct settings.
144
+
145
+ ```
146
+ # Step 1: Remove existing protection
147
+ MCP: gitlab_ops
148
+ Tool: mcp_gitlab_ops_unprotect_branch
149
+
150
+ Parameters:
151
+ project_id: <project_id>
152
+ branch: "main"
153
+
154
+ # Step 2: Re-protect with M settings
155
+ MCP: gitlab_ops
156
+ Tool: mcp_gitlab_ops_protect_branch
157
+
158
+ Parameters:
159
+ project_id: <project_id>
160
+ branch: "main"
161
+ push_access_level: 0 # no one pushes directly
162
+ merge_access_level: 40 # maintainer-level merge
163
+ allow_force_push: false
164
+ ```
165
+
166
+ **Gotchas:**
167
+ - `protect_branch` returns **409 Conflict** if protection already exists.
168
+ Always unprotect first.
169
+ - Seed files (push_files) must complete BEFORE protect_branch. Once
170
+ `push_access_level: 0` is set, even the MCP token cannot push
171
+ directly — only MR merges work.
172
+
173
+ ---
174
+
175
+ ## scm.create_access_token
176
+
177
+ Create a project access token for the merge transaction pipeline.
178
+
179
+ ```
180
+ MCP: gitlab_ops
181
+ Tool: mcp_gitlab_ops_create_project_access_token
182
+
183
+ Parameters:
184
+ project_id: <project_id>
185
+ name: "m-merge-transaction"
186
+ scopes: ["api", "read_repository", "write_repository"]
187
+ access_level: 40 # maintainer
188
+ expires_at: <1-year-from-now>
189
+
190
+ Returns: token value (store securely — shown only once)
191
+ ```
192
+
193
+ **Gotchas:**
194
+ - **Premium+ only.** Project access tokens are not available on GitLab
195
+ free tier.
196
+ - **Free tier fallback:** Use a personal access token (PAT) with `api`
197
+ scope that covers the entire group. The same PAT is stored once as a
198
+ CI variable on the root repo and used for all managed repos. Less
199
+ granular (one token for everything vs one per repo) but functionally
200
+ equivalent.
201
+
202
+ ---
203
+
204
+ ## scm.create_pipeline_trigger
205
+
206
+ Create a pipeline trigger token on a repo. Used by managed repo webhooks
207
+ to kick off shadow integration.
208
+
209
+ ```
210
+ MCP: gitlab_ops
211
+ Tool: mcp_gitlab_ops_create_pipeline_trigger
212
+
213
+ Parameters:
214
+ project_id: <root-project-id>
215
+ description: "m-shadow-integration-trigger"
216
+
217
+ Returns: trigger token value
218
+ ```
219
+
220
+ ---
221
+
222
+ ## scm.create_webhook
223
+
224
+ Install a webhook on a repo that fires on specific events.
225
+
226
+ ```
227
+ MCP: gitlab_ops
228
+ Tool: mcp_gitlab_ops_create_webhook
229
+
230
+ Parameters:
231
+ project_id: <project_id>
232
+ url: "https://gitlab.com/api/v4/projects/<root-project-id>/trigger/pipeline?token=<trigger-token>&ref=main"
233
+ merge_requests_events: true
234
+ push_events: false # CRITICAL — see note
235
+ enable_ssl_verification: true
236
+ ```
237
+
238
+ **Gotchas:**
239
+ - GitLab **defaults `push_events` to `true`** when creating a webhook,
240
+ even if not specified in the API call. You MUST explicitly set
241
+ `push_events: false`. Without this, every push to a managed repo
242
+ triggers a root repo pipeline — causing noise and wasted CI minutes.
243
+
244
+ ---
245
+
246
+ ## scm.store_ci_secret
247
+
248
+ Store a secret as a CI variable on a repo.
249
+
250
+ ```
251
+ MCP: gitlab_ops
252
+ Tool: mcp_gitlab_ops_create_ci_variable
253
+
254
+ Parameters:
255
+ project_id: <project_id>
256
+ key: <key> # e.g. M_TOKEN_API_READ
257
+ value: <value>
258
+ protected: true # only on protected branches
259
+ masked: true # hidden in job logs
260
+ ```
261
+
262
+ ---
263
+
264
+ ## scm.post_commit_status
265
+
266
+ Report a build status (pass/fail) on a specific commit. Used by shadow
267
+ integration to push results back to managed repo MRs.
268
+
269
+ ```
270
+ GitLab API (direct HTTP call — no MCP tool available):
271
+
272
+ POST /projects/<project_id>/statuses/<commit_sha>
273
+ Headers:
274
+ PRIVATE-TOKEN: <M_GROUP_TOKEN>
275
+ Body:
276
+ state: success | failed
277
+ name: "shadow-integration"
278
+ description: <human-readable message>
279
+ target_url: <link to root pipeline>
280
+ ```
281
+
282
+ **Gotchas:**
283
+ - Requires a token with `api` scope on the target repo. The
284
+ `M_GROUP_TOKEN` (group-level token) covers all managed repos.
285
+ - This is typically called from CI scripts, not from MCP tools.
286
+ The root repo's `.gitlab-ci.yml` shadow:report-status job uses
287
+ `curl` to call this API.
288
+
289
+ ---
290
+
291
+ ## scm.create_branch
292
+
293
+ Create a new branch from an existing ref.
294
+
295
+ ```
296
+ MCP: gitlab
297
+ Tool: mcp_gitlab_create_branch
298
+
299
+ Parameters:
300
+ project_id: <project_id>
301
+ branch: <branch-name>
302
+ ref: <source-ref> # branch name, tag, or SHA
303
+ ```
304
+
305
+ ---
306
+
307
+ ## scm.create_merge_request
308
+
309
+ Create a merge request.
310
+
311
+ ```
312
+ MCP: gitlab
313
+ Tool: mcp_gitlab_create_merge_request
314
+
315
+ Parameters:
316
+ project_id: <project_id>
317
+ source_branch: <source-branch>
318
+ target_branch: <target-branch>
319
+ title: <title>
320
+ description: <description>
321
+ remove_source_branch: true
322
+
323
+ Returns: MR URL (from web_url field)
324
+ ```
325
+
326
+ ---
327
+
328
+ ## CI-Specific Notes
329
+
330
+ ### Pipeline configuration file
331
+
332
+ GitLab CI uses `.gitlab-ci.yml` at the repo root.
333
+
334
+ ### Workflow rules (prevent duplicate pipelines)
335
+
336
+ ```yaml
337
+ workflow:
338
+ rules:
339
+ - if: $CI_MERGE_REQUEST_IID
340
+ - if: $CI_COMMIT_BRANCH == "main"
341
+ ```
342
+
343
+ Without workflow rules, GitLab creates two pipelines for the same commit
344
+ when an MR exists: one for the branch push, one for the MR event.
345
+
346
+ ### Resource groups (merge transaction serialisation)
347
+
348
+ ```yaml
349
+ merge-transaction:
350
+ resource_group: distributed_merge
351
+ ```
352
+
353
+ Ensures only one merge transaction runs at a time across the entire project.
354
+
355
+ ### Docker-in-Docker for compose
356
+
357
+ ```yaml
358
+ image: docker:latest
359
+ services:
360
+ - docker:dind
361
+ variables:
362
+ DOCKER_TLS_CERTDIR: "/certs"
363
+ ```
364
+
365
+ **DinD networking:** Published ports bind on the `docker` service
366
+ container's network interface. Health checks must use hostname `docker`,
367
+ not `localhost`:
368
+ ```
369
+ DOCKER_GATEWAY="${DOCKER_GATEWAY:-docker}"
370
+ curl http://${DOCKER_GATEWAY}:3000/health
371
+ ```
372
+
373
+ ### Project settings
374
+
375
+ After scaffolding, set `only_allow_merge_if_pipeline_succeeds: true`
376
+ on every repo. Without this, MRs can be merged while the pipeline is
377
+ still running or failing.