methodology-framework 0.2.0__tar.gz → 0.3.3__tar.gz

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 (35) hide show
  1. {methodology_framework-0.2.0/src/methodology_framework.egg-info → methodology_framework-0.3.3}/PKG-INFO +135 -24
  2. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/README.md +134 -23
  3. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/pyproject.toml +2 -1
  4. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/build_playbook.py +30 -9
  5. methodology_framework-0.3.3/src/methodology_framework/jira_discovery.py +397 -0
  6. methodology_framework-0.3.3/src/methodology_framework/playbooks/bindings/meth.yaml +10 -0
  7. methodology_framework-0.3.3/src/methodology_framework/playbooks/bindings/scrum.yaml +11 -0
  8. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/playbooks/scrum-router.body.md +35 -25
  9. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/sync_stories_to_jira.py +250 -138
  10. {methodology_framework-0.2.0 → methodology_framework-0.3.3/src/methodology_framework.egg-info}/PKG-INFO +135 -24
  11. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework.egg-info/SOURCES.txt +4 -0
  12. methodology_framework-0.3.3/tests/test_jira_discovery.py +251 -0
  13. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/tests/test_sync_stories_to_jira.py +233 -167
  14. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/tests/test_workflows.py +3 -5
  15. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/LICENSE +0 -0
  16. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/setup.cfg +0 -0
  17. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/__init__.py +0 -0
  18. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/__main__.py +0 -0
  19. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/bootstrap_jira.py +0 -0
  20. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/jira_shapes/__init__.py +0 -0
  21. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/jira_shapes/automation_rules.yaml +0 -0
  22. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/jira_shapes/custom_fields.yaml +0 -0
  23. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/jira_shapes/workflow.yaml +0 -0
  24. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/populate_acus.py +0 -0
  25. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/register_playbook_with_devin.py +0 -0
  26. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/specs/devin-story-format.md +0 -0
  27. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/templates/github_workflows/populate-story-acus-caller.yml +0 -0
  28. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework/templates/story.md +0 -0
  29. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework.egg-info/dependency_links.txt +0 -0
  30. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework.egg-info/requires.txt +0 -0
  31. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/src/methodology_framework.egg-info/top_level.txt +0 -0
  32. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/tests/test_bootstrap_jira.py +0 -0
  33. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/tests/test_build_playbook.py +0 -0
  34. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/tests/test_populate_acus.py +0 -0
  35. {methodology_framework-0.2.0 → methodology_framework-0.3.3}/tests/test_register_playbook_with_devin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: methodology-framework
3
- Version: 0.2.0
3
+ Version: 0.3.3
4
4
  Summary: Portable process tooling for agent-driven delivery — scripts, playbooks, templates, and specs.
5
5
  Author: whiteout59
6
6
  License-Expression: Apache-2.0
@@ -163,10 +163,63 @@ framework's workflows by SemVer tag.
163
163
  > in the caller so updates are deliberate, not silent. This matches the
164
164
  > version-pinning model per methodology requirements § 4.13.
165
165
 
166
+ ### Required inputs
167
+
168
+ | Input | Required | Default | Description |
169
+ |-------|----------|---------|-------------|
170
+ | `project-key` | **Yes** | — | Jira project key (e.g. `METH`, `SCRUM`). No default — sync errors if absent. |
171
+ | `jira-base-url` | **Yes** | — | Jira Cloud base URL (e.g. `https://icpipeline.atlassian.net`). |
172
+ | `jira-user-email` | **Yes** | — | Jira service account email for HTTP Basic auth. |
173
+ | `stories-dir` | No | `docs/stories` | Relative path to the stories directory. |
174
+ | `phase-regex` | No | `^phase\d+$` | Regex for phase directory matching. Empty string (`""`) disables phase labeling entirely. |
175
+ | `repo-url` | No | *(auto)* | Override repo URL. Normally derived from `GITHUB_SERVER_URL` + `GITHUB_REPOSITORY` (GHA-native). |
176
+
177
+ ### Per-tenant discovery
178
+
179
+ Sync discovers all tenant-specific Jira IDs at runtime via the REST API,
180
+ using canonical **names** as lookup keys. The names come from the framework's
181
+ shape definitions (`jira_shapes/*.yaml`) — the same spec the verifier
182
+ (`bootstrap-jira --verify`) enforces.
183
+
184
+ **Custom fields** (discovered via `GET /rest/api/3/field`):
185
+
186
+ | Name | Purpose |
187
+ |------|---------|
188
+ | `Requirement IDs` | Multi-value field for REQ-id traceability |
189
+ | `Story File` | URL to the canonical story `.md` file in the repo |
190
+ | `Agent Estimate` | Numeric estimate in agent-hours |
191
+
192
+ **Statuses** (discovered via `GET /rest/api/3/project/{key}/statuses`):
193
+
194
+ | Name | Category |
195
+ |------|----------|
196
+ | `To Do` | TODO |
197
+ | `Ready for AI agent` | TODO |
198
+ | `In Progress` | IN_PROGRESS |
199
+ | `In Review` | IN_PROGRESS |
200
+ | `Waiting` | IN_PROGRESS |
201
+ | `Blocked` | IN_PROGRESS |
202
+ | `Done` | DONE |
203
+ | `Won't do` | DONE |
204
+
205
+ **Transitions**: Sourced from the workflow spec (`jira_shapes/workflow.yaml`).
206
+
207
+ **Link types** (discovered via `GET /rest/api/3/issueLinkType`):
208
+ `Blocks` (inward: "is blocked by").
209
+
210
+ **Issue types** (discovered via `GET /rest/api/3/issuetype`):
211
+ `Story`, `Sub-task`.
212
+
213
+ If any required name is missing from the target project, sync fails fast:
214
+ ```
215
+ ERROR: Required custom field 'Story File' not found in Jira project METH;
216
+ run 'methodology-framework bootstrap-jira --verify --project-key=METH' to identify the gap.
217
+ ```
218
+
166
219
  ### Story sync workflow
167
220
 
168
221
  Syncs story `.md` files from the adopter's repo to Jira. Create
169
- `.github/workflows/sync.yml` in the adopter repo:
222
+ `.github/workflows/sync-stories.yml` in the adopter repo:
170
223
 
171
224
  ```yaml
172
225
  name: Sync stories to Jira
@@ -189,17 +242,17 @@ on:
189
242
 
190
243
  jobs:
191
244
  sync:
192
- uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.1.0
245
+ permissions:
246
+ contents: write
247
+ pull-requests: read
248
+ uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.3.0
193
249
  with:
194
- project_key: SCRUM
195
- repo: whiteout59/centralized-pipeline-ui
196
- story_path_pattern: "docs/stories/{phase1,phase2}/**/*.md"
197
- cf_story_file: "customfield_10073"
198
- cf_requirement_ids: "customfield_10141"
199
- cf_agent_estimate: "customfield_10074"
250
+ project_key: MYPROJ
200
251
  mode: ${{ github.event.inputs.mode || 'since-ref' }}
201
252
  jira_base_url: "https://myorg.atlassian.net"
202
253
  jira_user_email: "devin-sync@myorg.atlassian.net"
254
+ # stories_dir: "docs/stories" # default
255
+ # phase_regex: "^phase\\d+$" # default; set "" to disable
203
256
  secrets:
204
257
  JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
205
258
  ```
@@ -213,6 +266,34 @@ for nightly or `workflow_dispatch` full-corpus syncs.
213
266
  > inherited by default in reusable workflows. Using `secrets: inherit`
214
267
  > works but is brittle — prefer explicit mapping.
215
268
 
269
+ #### Required caller permissions
270
+
271
+ The reusable sync workflow declares `permissions: contents: write`
272
+ internally (for `jira_key` + `agent_acus` writeback commits). GitHub
273
+ Actions requires the **caller** to grant at least the same permissions
274
+ at the job level — otherwise the workflow fails at the `uses:`
275
+ resolution step with:
276
+
277
+ ```
278
+ The workflow is requesting 'contents: write', but is only allowed 'contents: read'.
279
+ ```
280
+
281
+ Add a job-level `permissions:` block to your caller:
282
+
283
+ ```yaml
284
+ jobs:
285
+ sync:
286
+ permissions:
287
+ contents: write
288
+ pull-requests: read
289
+ uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.2.0
290
+ # ... with: / secrets: as above
291
+ ```
292
+
293
+ > **Job-scoped, not workflow-scoped.** Keep `permissions:` on the job,
294
+ > not the top-level `on:` — if future jobs are added to the same file,
295
+ > they should not inherit write access they don't need.
296
+
216
297
  ### Playbook build + register workflow
217
298
 
218
299
  Builds a concrete playbook from a parameterized body + bindings file,
@@ -244,6 +325,16 @@ jobs:
244
325
  > 4 levels deep. If your repo wraps these workflows in another
245
326
  > caller layer, verify the total nesting depth stays within limits.
246
327
 
328
+ ## How this repo evolves
329
+
330
+ The methodology-framework repo itself uses the same sync workflow it
331
+ ships to adopters. Framework-side evolution stories (new specs, CLI
332
+ features, workflow improvements) are drafted as `.md` files under
333
+ `docs/stories/` and synced to the **METH** Jira project via
334
+ [`.github/workflows/sync-stories.yml`](.github/workflows/sync-stories.yml).
335
+ Adopter-side stories (product-specific work) file in each adopter's own
336
+ Jira project (e.g. SCRUM for centralized-pipeline-ui).
337
+
247
338
  ## PyPI Publishing
248
339
 
249
340
  The package is published to PyPI automatically via OIDC Trusted Publishers
@@ -268,31 +359,37 @@ to the GitHub repo `whiteout59/methodology-framework`, workflow
268
359
  Version bumps in `pyproject.toml` drive the entire release cycle
269
360
  automatically. The operator's only decision surface is PR review.
270
361
 
271
- ### Normal flow
362
+ ### Primary flow (chained via `workflow_call`)
272
363
 
273
- 1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.1.1"` → `"0.1.2"`).
364
+ 1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.2.0"` → `"0.2.1"`).
274
365
  2. Reviewer approves and merges the PR to `main`.
275
- 3. The `auto-tag.yml` workflow detects the `pyproject.toml` change, extracts the
276
- version, validates it as stable SemVer (`^[0-9]+\.[0-9]+\.[0-9]+$`), and
277
- creates an annotated tag `v0.1.2`.
278
- 4. The `pypi-release.yml` workflow fires on the new tag and publishes the
279
- package to PyPI via OIDC Trusted Publishers.
366
+ 3. `auto-tag.yml` fires on the `pyproject.toml` change, extracts the version,
367
+ validates it as stable SemVer (`^[0-9]+\.[0-9]+\.[0-9]+$`), and creates an
368
+ annotated tag `v0.2.1`.
369
+ 4. `auto-tag.yml`'s `publish` job invokes `pypi-release.yml` via `workflow_call`,
370
+ passing the version as an input.
371
+ 5. `pypi-release.yml` checks out the tag, builds the package, and publishes to
372
+ PyPI via OIDC Trusted Publishers.
280
373
 
281
- **Operator surface = PR review only.** No manual `git tag` step required.
374
+ **Operator surface = PR review only.** No manual `git tag` step, no manual
375
+ publish trigger. The `workflow_call` chain bypasses GitHub's recursion guard
376
+ that prevents `GITHUB_TOKEN`-authored tag pushes from triggering downstream
377
+ workflows via `on: push: tags:`.
282
378
 
283
- ### Manual fallback
379
+ ### Fallback (operator-pushed tag)
284
380
 
285
381
  If `auto-tag.yml` doesn't fire (workflow disabled, branch protection
286
- blocking the tag push, GitHub Actions outage, etc.), the manual fallback
287
- still works:
382
+ blocking the tag push, GitHub Actions outage, etc.), or the operator needs
383
+ to re-publish a yanked version, the manual fallback still works:
288
384
 
289
385
  ```bash
290
- git tag -a v0.1.2 -m "Release v0.1.2"
291
- git push origin v0.1.2
386
+ git tag -a vX.Y.Z -m "Release vX.Y.Z"
387
+ git push origin vX.Y.Z
292
388
  ```
293
389
 
294
- `pypi-release.yml` doesn't care how the tag arrived — it fires on any
295
- tag matching `v[0-9]+.[0-9]+.[0-9]+`.
390
+ An operator-pushed tag directly triggers `pypi-release.yml` via the
391
+ preserved `on: push: tags: ['v[0-9]+.[0-9]+.[0-9]+']` trigger. This
392
+ path is independent of `auto-tag.yml` and requires no `workflow_call`.
296
393
 
297
394
  ### Pre-release versions
298
395
 
@@ -326,6 +423,20 @@ The caller fires on merged PRs that touch story files, invokes the
326
423
  reusable `populate-story-acus.yml` workflow, and opens a follow-on PR
327
424
  with the populated ACU values.
328
425
 
426
+ ## Contributing
427
+
428
+ Framework evolution stories — new specs, CLI features, workflow improvements —
429
+ are filed as Markdown story files under [`docs/stories/`](docs/stories/).
430
+ See the [directory README](docs/stories/README.md) for the filing convention
431
+ (slug-named directories, frontmatter shape, `depends_on` hygiene). Stories
432
+ sync to the **METH** Jira project via the same sync workflow the framework
433
+ ships to adopters.
434
+
435
+ For the canonical story template and format spec, see
436
+ [`whiteout59/centralized-pipeline-ui/methodology/templates/story.md`](https://github.com/whiteout59/centralized-pipeline-ui/blob/main/methodology/templates/story.md)
437
+ and
438
+ [`whiteout59/centralized-pipeline-ui/methodology/devin-story-format.md`](https://github.com/whiteout59/centralized-pipeline-ui/blob/main/methodology/devin-story-format.md).
439
+
329
440
  ## Development
330
441
 
331
442
  ```bash
@@ -139,10 +139,63 @@ framework's workflows by SemVer tag.
139
139
  > in the caller so updates are deliberate, not silent. This matches the
140
140
  > version-pinning model per methodology requirements § 4.13.
141
141
 
142
+ ### Required inputs
143
+
144
+ | Input | Required | Default | Description |
145
+ |-------|----------|---------|-------------|
146
+ | `project-key` | **Yes** | — | Jira project key (e.g. `METH`, `SCRUM`). No default — sync errors if absent. |
147
+ | `jira-base-url` | **Yes** | — | Jira Cloud base URL (e.g. `https://icpipeline.atlassian.net`). |
148
+ | `jira-user-email` | **Yes** | — | Jira service account email for HTTP Basic auth. |
149
+ | `stories-dir` | No | `docs/stories` | Relative path to the stories directory. |
150
+ | `phase-regex` | No | `^phase\d+$` | Regex for phase directory matching. Empty string (`""`) disables phase labeling entirely. |
151
+ | `repo-url` | No | *(auto)* | Override repo URL. Normally derived from `GITHUB_SERVER_URL` + `GITHUB_REPOSITORY` (GHA-native). |
152
+
153
+ ### Per-tenant discovery
154
+
155
+ Sync discovers all tenant-specific Jira IDs at runtime via the REST API,
156
+ using canonical **names** as lookup keys. The names come from the framework's
157
+ shape definitions (`jira_shapes/*.yaml`) — the same spec the verifier
158
+ (`bootstrap-jira --verify`) enforces.
159
+
160
+ **Custom fields** (discovered via `GET /rest/api/3/field`):
161
+
162
+ | Name | Purpose |
163
+ |------|---------|
164
+ | `Requirement IDs` | Multi-value field for REQ-id traceability |
165
+ | `Story File` | URL to the canonical story `.md` file in the repo |
166
+ | `Agent Estimate` | Numeric estimate in agent-hours |
167
+
168
+ **Statuses** (discovered via `GET /rest/api/3/project/{key}/statuses`):
169
+
170
+ | Name | Category |
171
+ |------|----------|
172
+ | `To Do` | TODO |
173
+ | `Ready for AI agent` | TODO |
174
+ | `In Progress` | IN_PROGRESS |
175
+ | `In Review` | IN_PROGRESS |
176
+ | `Waiting` | IN_PROGRESS |
177
+ | `Blocked` | IN_PROGRESS |
178
+ | `Done` | DONE |
179
+ | `Won't do` | DONE |
180
+
181
+ **Transitions**: Sourced from the workflow spec (`jira_shapes/workflow.yaml`).
182
+
183
+ **Link types** (discovered via `GET /rest/api/3/issueLinkType`):
184
+ `Blocks` (inward: "is blocked by").
185
+
186
+ **Issue types** (discovered via `GET /rest/api/3/issuetype`):
187
+ `Story`, `Sub-task`.
188
+
189
+ If any required name is missing from the target project, sync fails fast:
190
+ ```
191
+ ERROR: Required custom field 'Story File' not found in Jira project METH;
192
+ run 'methodology-framework bootstrap-jira --verify --project-key=METH' to identify the gap.
193
+ ```
194
+
142
195
  ### Story sync workflow
143
196
 
144
197
  Syncs story `.md` files from the adopter's repo to Jira. Create
145
- `.github/workflows/sync.yml` in the adopter repo:
198
+ `.github/workflows/sync-stories.yml` in the adopter repo:
146
199
 
147
200
  ```yaml
148
201
  name: Sync stories to Jira
@@ -165,17 +218,17 @@ on:
165
218
 
166
219
  jobs:
167
220
  sync:
168
- uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.1.0
221
+ permissions:
222
+ contents: write
223
+ pull-requests: read
224
+ uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.3.0
169
225
  with:
170
- project_key: SCRUM
171
- repo: whiteout59/centralized-pipeline-ui
172
- story_path_pattern: "docs/stories/{phase1,phase2}/**/*.md"
173
- cf_story_file: "customfield_10073"
174
- cf_requirement_ids: "customfield_10141"
175
- cf_agent_estimate: "customfield_10074"
226
+ project_key: MYPROJ
176
227
  mode: ${{ github.event.inputs.mode || 'since-ref' }}
177
228
  jira_base_url: "https://myorg.atlassian.net"
178
229
  jira_user_email: "devin-sync@myorg.atlassian.net"
230
+ # stories_dir: "docs/stories" # default
231
+ # phase_regex: "^phase\\d+$" # default; set "" to disable
179
232
  secrets:
180
233
  JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
181
234
  ```
@@ -189,6 +242,34 @@ for nightly or `workflow_dispatch` full-corpus syncs.
189
242
  > inherited by default in reusable workflows. Using `secrets: inherit`
190
243
  > works but is brittle — prefer explicit mapping.
191
244
 
245
+ #### Required caller permissions
246
+
247
+ The reusable sync workflow declares `permissions: contents: write`
248
+ internally (for `jira_key` + `agent_acus` writeback commits). GitHub
249
+ Actions requires the **caller** to grant at least the same permissions
250
+ at the job level — otherwise the workflow fails at the `uses:`
251
+ resolution step with:
252
+
253
+ ```
254
+ The workflow is requesting 'contents: write', but is only allowed 'contents: read'.
255
+ ```
256
+
257
+ Add a job-level `permissions:` block to your caller:
258
+
259
+ ```yaml
260
+ jobs:
261
+ sync:
262
+ permissions:
263
+ contents: write
264
+ pull-requests: read
265
+ uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.2.0
266
+ # ... with: / secrets: as above
267
+ ```
268
+
269
+ > **Job-scoped, not workflow-scoped.** Keep `permissions:` on the job,
270
+ > not the top-level `on:` — if future jobs are added to the same file,
271
+ > they should not inherit write access they don't need.
272
+
192
273
  ### Playbook build + register workflow
193
274
 
194
275
  Builds a concrete playbook from a parameterized body + bindings file,
@@ -220,6 +301,16 @@ jobs:
220
301
  > 4 levels deep. If your repo wraps these workflows in another
221
302
  > caller layer, verify the total nesting depth stays within limits.
222
303
 
304
+ ## How this repo evolves
305
+
306
+ The methodology-framework repo itself uses the same sync workflow it
307
+ ships to adopters. Framework-side evolution stories (new specs, CLI
308
+ features, workflow improvements) are drafted as `.md` files under
309
+ `docs/stories/` and synced to the **METH** Jira project via
310
+ [`.github/workflows/sync-stories.yml`](.github/workflows/sync-stories.yml).
311
+ Adopter-side stories (product-specific work) file in each adopter's own
312
+ Jira project (e.g. SCRUM for centralized-pipeline-ui).
313
+
223
314
  ## PyPI Publishing
224
315
 
225
316
  The package is published to PyPI automatically via OIDC Trusted Publishers
@@ -244,31 +335,37 @@ to the GitHub repo `whiteout59/methodology-framework`, workflow
244
335
  Version bumps in `pyproject.toml` drive the entire release cycle
245
336
  automatically. The operator's only decision surface is PR review.
246
337
 
247
- ### Normal flow
338
+ ### Primary flow (chained via `workflow_call`)
248
339
 
249
- 1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.1.1"` → `"0.1.2"`).
340
+ 1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.2.0"` → `"0.2.1"`).
250
341
  2. Reviewer approves and merges the PR to `main`.
251
- 3. The `auto-tag.yml` workflow detects the `pyproject.toml` change, extracts the
252
- version, validates it as stable SemVer (`^[0-9]+\.[0-9]+\.[0-9]+$`), and
253
- creates an annotated tag `v0.1.2`.
254
- 4. The `pypi-release.yml` workflow fires on the new tag and publishes the
255
- package to PyPI via OIDC Trusted Publishers.
342
+ 3. `auto-tag.yml` fires on the `pyproject.toml` change, extracts the version,
343
+ validates it as stable SemVer (`^[0-9]+\.[0-9]+\.[0-9]+$`), and creates an
344
+ annotated tag `v0.2.1`.
345
+ 4. `auto-tag.yml`'s `publish` job invokes `pypi-release.yml` via `workflow_call`,
346
+ passing the version as an input.
347
+ 5. `pypi-release.yml` checks out the tag, builds the package, and publishes to
348
+ PyPI via OIDC Trusted Publishers.
256
349
 
257
- **Operator surface = PR review only.** No manual `git tag` step required.
350
+ **Operator surface = PR review only.** No manual `git tag` step, no manual
351
+ publish trigger. The `workflow_call` chain bypasses GitHub's recursion guard
352
+ that prevents `GITHUB_TOKEN`-authored tag pushes from triggering downstream
353
+ workflows via `on: push: tags:`.
258
354
 
259
- ### Manual fallback
355
+ ### Fallback (operator-pushed tag)
260
356
 
261
357
  If `auto-tag.yml` doesn't fire (workflow disabled, branch protection
262
- blocking the tag push, GitHub Actions outage, etc.), the manual fallback
263
- still works:
358
+ blocking the tag push, GitHub Actions outage, etc.), or the operator needs
359
+ to re-publish a yanked version, the manual fallback still works:
264
360
 
265
361
  ```bash
266
- git tag -a v0.1.2 -m "Release v0.1.2"
267
- git push origin v0.1.2
362
+ git tag -a vX.Y.Z -m "Release vX.Y.Z"
363
+ git push origin vX.Y.Z
268
364
  ```
269
365
 
270
- `pypi-release.yml` doesn't care how the tag arrived — it fires on any
271
- tag matching `v[0-9]+.[0-9]+.[0-9]+`.
366
+ An operator-pushed tag directly triggers `pypi-release.yml` via the
367
+ preserved `on: push: tags: ['v[0-9]+.[0-9]+.[0-9]+']` trigger. This
368
+ path is independent of `auto-tag.yml` and requires no `workflow_call`.
272
369
 
273
370
  ### Pre-release versions
274
371
 
@@ -302,6 +399,20 @@ The caller fires on merged PRs that touch story files, invokes the
302
399
  reusable `populate-story-acus.yml` workflow, and opens a follow-on PR
303
400
  with the populated ACU values.
304
401
 
402
+ ## Contributing
403
+
404
+ Framework evolution stories — new specs, CLI features, workflow improvements —
405
+ are filed as Markdown story files under [`docs/stories/`](docs/stories/).
406
+ See the [directory README](docs/stories/README.md) for the filing convention
407
+ (slug-named directories, frontmatter shape, `depends_on` hygiene). Stories
408
+ sync to the **METH** Jira project via the same sync workflow the framework
409
+ ships to adopters.
410
+
411
+ For the canonical story template and format spec, see
412
+ [`whiteout59/centralized-pipeline-ui/methodology/templates/story.md`](https://github.com/whiteout59/centralized-pipeline-ui/blob/main/methodology/templates/story.md)
413
+ and
414
+ [`whiteout59/centralized-pipeline-ui/methodology/devin-story-format.md`](https://github.com/whiteout59/centralized-pipeline-ui/blob/main/methodology/devin-story-format.md).
415
+
305
416
  ## Development
306
417
 
307
418
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "methodology-framework"
7
- version = "0.2.0"
7
+ version = "0.3.3"
8
8
  description = "Portable process tooling for agent-driven delivery — scripts, playbooks, templates, and specs."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -37,6 +37,7 @@ where = ["src"]
37
37
  [tool.setuptools.package-data]
38
38
  methodology_framework = [
39
39
  "playbooks/*.md",
40
+ "playbooks/bindings/*.yaml",
40
41
  "templates/*.md",
41
42
  "templates/github_workflows/*.yml",
42
43
  "specs/*.md",
@@ -10,15 +10,21 @@ documentation remains legible in the rendered output. The frontmatter
10
10
  ``version:`` value is injected as the ``{{VERSION}}`` binding automatically.
11
11
 
12
12
  Usage:
13
- python scripts/build_playbook.py [--output PATH] [--check]
13
+ python -m methodology_framework.build_playbook \
14
+ [--body PATH] [--bindings PATH] [--output PATH] [--check]
14
15
 
15
16
  Options:
17
+ --body PATH Path to the parameterized playbook body (default: auto-detected
18
+ relative to repo root).
19
+ --bindings PATH Path to the bindings file containing a playbook_bindings: YAML block
20
+ (default: auto-detected relative to repo root).
16
21
  --output PATH Write the built playbook to PATH (default: stdout).
17
22
  --check Validate only: exit 0 if no unreplaced tokens, exit 1 otherwise.
18
23
  Does not write output.
19
24
 
20
- The script is designed to be run from the repo root. It uses relative paths to locate
21
- the body and bindings files.
25
+ When --body and --bindings are omitted, the script falls back to the legacy
26
+ hardcoded paths (methodology/playbooks/scrum-router.body.md and
27
+ docs/jira-pickup-config.md relative to the repo root).
22
28
  """
23
29
 
24
30
  from __future__ import annotations
@@ -112,6 +118,18 @@ def find_unreplaced(text: str) -> list[str]:
112
118
 
113
119
  def main() -> int:
114
120
  parser = argparse.ArgumentParser(description=__doc__)
121
+ parser.add_argument(
122
+ "--body",
123
+ type=str,
124
+ default=None,
125
+ help="Path to the parameterized playbook body (default: auto-detected)",
126
+ )
127
+ parser.add_argument(
128
+ "--bindings",
129
+ type=str,
130
+ default=None,
131
+ help="Path to bindings file with playbook_bindings: YAML block",
132
+ )
115
133
  parser.add_argument(
116
134
  "--output",
117
135
  type=str,
@@ -123,16 +141,19 @@ def main() -> int:
123
141
  )
124
142
  args = parser.parse_args()
125
143
 
126
- if not BODY_PATH.exists():
127
- print(f"ERROR: Body file not found: {BODY_PATH}", file=sys.stderr)
144
+ body_path = Path(args.body) if args.body else BODY_PATH
145
+ config_path = Path(args.bindings) if args.bindings else CONFIG_PATH
146
+
147
+ if not body_path.exists():
148
+ print(f"ERROR: Body file not found: {body_path}", file=sys.stderr)
128
149
  return 1
129
150
 
130
- if not CONFIG_PATH.exists():
131
- print(f"ERROR: Config file not found: {CONFIG_PATH}", file=sys.stderr)
151
+ if not config_path.exists():
152
+ print(f"ERROR: Config/bindings file not found: {config_path}", file=sys.stderr)
132
153
  return 1
133
154
 
134
- body = BODY_PATH.read_text()
135
- config = CONFIG_PATH.read_text()
155
+ body = body_path.read_text()
156
+ config = config_path.read_text()
136
157
 
137
158
  bindings = extract_bindings(config)
138
159
  if not bindings: