methodology-framework 0.2.0__tar.gz → 0.3.2__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.
- {methodology_framework-0.2.0/src/methodology_framework.egg-info → methodology_framework-0.3.2}/PKG-INFO +135 -24
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/README.md +134 -23
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/pyproject.toml +2 -1
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/build_playbook.py +30 -9
- methodology_framework-0.3.2/src/methodology_framework/jira_discovery.py +389 -0
- methodology_framework-0.3.2/src/methodology_framework/playbooks/bindings/meth.yaml +10 -0
- methodology_framework-0.3.2/src/methodology_framework/playbooks/bindings/scrum.yaml +11 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/playbooks/scrum-router.body.md +35 -25
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/sync_stories_to_jira.py +250 -138
- {methodology_framework-0.2.0 → methodology_framework-0.3.2/src/methodology_framework.egg-info}/PKG-INFO +135 -24
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework.egg-info/SOURCES.txt +4 -0
- methodology_framework-0.3.2/tests/test_jira_discovery.py +247 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/tests/test_sync_stories_to_jira.py +233 -167
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/tests/test_workflows.py +3 -5
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/LICENSE +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/setup.cfg +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/__init__.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/__main__.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/bootstrap_jira.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/jira_shapes/__init__.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/jira_shapes/automation_rules.yaml +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/jira_shapes/custom_fields.yaml +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/jira_shapes/workflow.yaml +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/populate_acus.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/register_playbook_with_devin.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/specs/devin-story-format.md +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/templates/github_workflows/populate-story-acus-caller.yml +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework/templates/story.md +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework.egg-info/dependency_links.txt +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework.egg-info/requires.txt +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/src/methodology_framework.egg-info/top_level.txt +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/tests/test_bootstrap_jira.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/tests/test_build_playbook.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/tests/test_populate_acus.py +0 -0
- {methodology_framework-0.2.0 → methodology_framework-0.3.2}/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
|
|
3
|
+
Version: 0.3.2
|
|
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
|
-
|
|
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:
|
|
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
|
-
###
|
|
362
|
+
### Primary flow (chained via `workflow_call`)
|
|
272
363
|
|
|
273
|
-
1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.
|
|
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.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
4.
|
|
279
|
-
|
|
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
|
|
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
|
-
###
|
|
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
|
|
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
|
|
291
|
-
git push origin
|
|
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`
|
|
295
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
###
|
|
338
|
+
### Primary flow (chained via `workflow_call`)
|
|
248
339
|
|
|
249
|
-
1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.
|
|
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.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
4.
|
|
255
|
-
|
|
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
|
|
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
|
-
###
|
|
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
|
|
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
|
|
267
|
-
git push origin
|
|
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`
|
|
271
|
-
|
|
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
|
|
7
|
+
version = "0.3.2"
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
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
|
|
127
|
-
|
|
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
|
|
131
|
-
print(f"ERROR: Config file not found: {
|
|
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 =
|
|
135
|
-
config =
|
|
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:
|