FlywheelIC 0.4.5__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 (39) hide show
  1. flywheelic-0.4.5/LICENSE +21 -0
  2. flywheelic-0.4.5/PKG-INFO +485 -0
  3. flywheelic-0.4.5/README.md +461 -0
  4. flywheelic-0.4.5/pyproject.toml +58 -0
  5. flywheelic-0.4.5/setup.cfg +4 -0
  6. flywheelic-0.4.5/src/FlywheelIC.egg-info/PKG-INFO +485 -0
  7. flywheelic-0.4.5/src/FlywheelIC.egg-info/SOURCES.txt +37 -0
  8. flywheelic-0.4.5/src/FlywheelIC.egg-info/dependency_links.txt +1 -0
  9. flywheelic-0.4.5/src/FlywheelIC.egg-info/requires.txt +14 -0
  10. flywheelic-0.4.5/src/FlywheelIC.egg-info/top_level.txt +1 -0
  11. flywheelic-0.4.5/src/methodology_framework/__init__.py +23 -0
  12. flywheelic-0.4.5/src/methodology_framework/__main__.py +28 -0
  13. flywheelic-0.4.5/src/methodology_framework/bootstrap_jira.py +702 -0
  14. flywheelic-0.4.5/src/methodology_framework/build_playbook.py +193 -0
  15. flywheelic-0.4.5/src/methodology_framework/jira_discovery.py +762 -0
  16. flywheelic-0.4.5/src/methodology_framework/jira_shapes/__init__.py +0 -0
  17. flywheelic-0.4.5/src/methodology_framework/jira_shapes/automation_rules.yaml +85 -0
  18. flywheelic-0.4.5/src/methodology_framework/jira_shapes/custom_fields.yaml +45 -0
  19. flywheelic-0.4.5/src/methodology_framework/jira_shapes/workflow.yaml +143 -0
  20. flywheelic-0.4.5/src/methodology_framework/playbooks/bindings/greet.yaml +13 -0
  21. flywheelic-0.4.5/src/methodology_framework/playbooks/bindings/meth.yaml +11 -0
  22. flywheelic-0.4.5/src/methodology_framework/playbooks/bindings/scrum.yaml +12 -0
  23. flywheelic-0.4.5/src/methodology_framework/playbooks/router-base.body.md +236 -0
  24. flywheelic-0.4.5/src/methodology_framework/populate_acus.py +358 -0
  25. flywheelic-0.4.5/src/methodology_framework/register_playbook_with_devin.py +341 -0
  26. flywheelic-0.4.5/src/methodology_framework/specs/devin-story-format.md +536 -0
  27. flywheelic-0.4.5/src/methodology_framework/sync_stories_to_jira.py +1352 -0
  28. flywheelic-0.4.5/src/methodology_framework/templates/github_workflows/populate-story-acus-caller.yml +29 -0
  29. flywheelic-0.4.5/src/methodology_framework/templates/story.md +152 -0
  30. flywheelic-0.4.5/tests/test_bootstrap_jira.py +719 -0
  31. flywheelic-0.4.5/tests/test_build_playbook.py +119 -0
  32. flywheelic-0.4.5/tests/test_jira_discovery.py +477 -0
  33. flywheelic-0.4.5/tests/test_jira_discovery_custom_fields_fallback.py +218 -0
  34. flywheelic-0.4.5/tests/test_jira_discovery_transitions.py +67 -0
  35. flywheelic-0.4.5/tests/test_populate_acus.py +498 -0
  36. flywheelic-0.4.5/tests/test_populate_acus_integration.py +97 -0
  37. flywheelic-0.4.5/tests/test_register_playbook_with_devin.py +439 -0
  38. flywheelic-0.4.5/tests/test_sync_stories_to_jira.py +1707 -0
  39. flywheelic-0.4.5/tests/test_workflows.py +191 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 whiteout59
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,485 @@
1
+ Metadata-Version: 2.4
2
+ Name: FlywheelIC
3
+ Version: 0.4.5
4
+ Summary: Portable process tooling for agent-driven delivery — scripts, playbooks, templates, and specs.
5
+ Author: whiteout59
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: requests<3,>=2.31
11
+ Requires-Dist: python-frontmatter<2,>=1.1
12
+ Requires-Dist: pyyaml<7,>=6.0
13
+ Requires-Dist: colorama<1,>=0.4
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest<9,>=8.0; extra == "dev"
16
+ Requires-Dist: requests-mock<2,>=1.12; extra == "dev"
17
+ Requires-Dist: ruff==0.15.14; extra == "dev"
18
+ Requires-Dist: mypy<2,>=1.10; extra == "dev"
19
+ Requires-Dist: types-requests<3,>=2.31; extra == "dev"
20
+ Requires-Dist: types-PyYAML<7,>=6.0; extra == "dev"
21
+ Requires-Dist: types-colorama<1,>=0.4; extra == "dev"
22
+ Requires-Dist: vcrpy<9,>=7.0; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ [![PyPI version](https://img.shields.io/pypi/v/FlywheelIC)](https://pypi.org/project/FlywheelIC/)
26
+ # methodology-framework
27
+
28
+ Portable process tooling for agent-driven delivery -- scripts, playbooks, templates, and specs.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install FlywheelIC
34
+ # or, from a source checkout:
35
+ pip install -e .
36
+ ```
37
+
38
+ ## Package contents
39
+
40
+ - `methodology_framework.sync_stories_to_jira` -- one-way repo-to-Jira story sync
41
+ - `methodology_framework.build_playbook` -- build a concrete playbook from a parameterized body + bindings
42
+ - `methodology_framework.register_playbook_with_devin` -- register a rendered playbook with Devin's API
43
+ - `methodology_framework/playbooks/` -- parameterized playbook bodies (package data)
44
+ - `methodology_framework/templates/` -- story and process templates (package data)
45
+ - `methodology_framework/specs/` -- format specs (package data)
46
+ - `methodology_framework.bootstrap_jira` -- verify a Jira project matches the canonical shape (read-only spec-compliance checker)
47
+ - `methodology_framework/jira_shapes/` -- canonical Jira shape definitions (YAML, package data)
48
+
49
+ ## Jira Bootstrap (Verify Mode)
50
+
51
+ The CLI checks whether a Jira project matches the methodology-framework's
52
+ canonical shape and reports gaps. It does **not** provision — operators apply
53
+ gaps via the Jira UI. This read-only design lets the tool run in CI as a gate.
54
+
55
+ > **v0.2.0 breaking change:** `--apply` and `--dry-run` were removed.
56
+ > The CLI is now a spec-compliance verifier. See `operator-verify-runbook.md`
57
+ > for the operator workflow.
58
+
59
+ ### Prerequisites
60
+
61
+ - Python 3.12+
62
+ - `pip install FlywheelIC` (or `pip install -e .` from source)
63
+ - For `--verify` mode: `JIRA_ADMIN_TOKEN` environment variable set to a Jira
64
+ API token (admin scope is **not** required; a regular user token with read
65
+ access is sufficient), and `JIRA_USER_EMAIL` set to the email of the
66
+ Atlassian account that owns the token
67
+
68
+ ### Usage
69
+
70
+ ```bash
71
+ # Verify — query Jira and report spec-compliance gaps (read-only)
72
+ export JIRA_ADMIN_TOKEN="<your-token>"
73
+ export JIRA_USER_EMAIL="<your-email>"
74
+ python -m methodology_framework bootstrap-jira \
75
+ --project-key=MYPROJ \
76
+ --jira-host=myorg.atlassian.net \
77
+ --verify
78
+
79
+ # JSON output (stable schema — safe for CI parsing)
80
+ python -m methodology_framework bootstrap-jira \
81
+ --project-key=MYPROJ \
82
+ --jira-host=myorg.atlassian.net \
83
+ --verify --output=json
84
+
85
+ # Markdown output
86
+ python -m methodology_framework bootstrap-jira \
87
+ --project-key=MYPROJ \
88
+ --jira-host=myorg.atlassian.net \
89
+ --verify --output=markdown
90
+
91
+ # Export — emit an importable config bundle (YAML); no API calls
92
+ python -m methodology_framework bootstrap-jira \
93
+ --project-key=MYPROJ \
94
+ --jira-host=myorg.atlassian.net \
95
+ --export /tmp/jira-bundle.yaml
96
+ ```
97
+
98
+ ### Exit codes
99
+
100
+ | Code | Meaning |
101
+ |------|---------|
102
+ | `0` | All spec items verified OK |
103
+ | `1` | One or more items missing (gaps found) |
104
+ | `2` | One or more items unverifiable (API limitation; operator must inspect manually) |
105
+ | `3` | Error (connectivity, auth, or runtime failure) |
106
+
107
+ ### Flags
108
+
109
+ | Flag | Required | Description |
110
+ |------|----------|-------------|
111
+ | `--project-key` | Yes | Jira project key (e.g. `SCRUM`). Substituted for `{{PROJECT_KEY}}` in shape defs. |
112
+ | `--jira-host` | Yes | Jira Cloud host (e.g. `myorg.atlassian.net`). No `https://` prefix. |
113
+ | `--verify` | No | Query Jira and report spec-compliance gaps (read-only). |
114
+ | `--export <path>` | No | Emit importable config bundle at `<path>`. No API calls. |
115
+ | `--output` | No | Output format for `--verify`: `text` (default), `json`, `markdown`. |
116
+
117
+ ### Environment variables
118
+
119
+ | Variable | When needed | Description |
120
+ |----------|-------------|-------------|
121
+ | `JIRA_ADMIN_TOKEN` | `--verify` mode | Jira API token. Admin scope is **not** required; a regular user token with read access is sufficient. **Never** accepted as a CLI flag. |
122
+ | `JIRA_USER_EMAIL` | `--verify` mode | Email of the Atlassian account that owns the API token. Used with the token for HTTP Basic auth. **Never** accepted as a CLI flag. |
123
+
124
+ ### JSON output schema (v0.2.0)
125
+
126
+ ```json
127
+ {
128
+ "spec_version": "0.2.0",
129
+ "project_key": "METH",
130
+ "verified_at": "2025-05-31T00:00:00Z",
131
+ "items": [
132
+ {"resource_type": "status", "name": "To Do", "result": "ok", "details": null},
133
+ {"resource_type": "custom_field", "name": "Story File", "result": "missing", "details": "Operator must create via Settings → Custom fields → Create"}
134
+ ],
135
+ "summary": {"total": 14, "ok": 12, "missing": 1, "unverifiable": 1},
136
+ "exit_code": 1
137
+ }
138
+ ```
139
+
140
+ Any breaking change to this schema requires a MAJOR version bump.
141
+
142
+ ### Shape definitions
143
+
144
+ The three canonical shape files shipped as package data:
145
+
146
+ - `jira_shapes/workflow.yaml` — workflow statuses (To Do, Ready for AI agent,
147
+ In Progress, In Review, Waiting, Blocked, Done, Won't do) and transitions
148
+ with actor permissions
149
+ - `jira_shapes/custom_fields.yaml` — Story File (URL), Requirement IDs
150
+ (labels), Agent Estimate (number)
151
+ - `jira_shapes/automation_rules.yaml` — PR-title-key auto-transition,
152
+ dependency resolution, BLOCKED protection
153
+
154
+ For full methodology context see the methodology requirements doc § 4.13.3
155
+ ("Jira Bootstrap CLI").
156
+
157
+ ## Adopter Integration
158
+
159
+ Adopting projects consume the methodology framework's CI pipelines via
160
+ **reusable GitHub Actions workflows**. Instead of copying workflow YAML
161
+ into each repo, adopters write a thin caller that references the
162
+ framework's workflows by SemVer tag.
163
+
164
+ > **Version pinning requirement:** adopters MUST pin to a specific tag
165
+ > (`@v0.X.Y`), never `@main`. The framework version must be explicit
166
+ > in the caller so updates are deliberate, not silent. This matches the
167
+ > version-pinning model per methodology requirements § 4.13.
168
+
169
+ ### Required inputs
170
+
171
+ | Input | Required | Default | Description |
172
+ |-------|----------|---------|-------------|
173
+ | `project-key` | **Yes** | — | Jira project key (e.g. `METH`, `SCRUM`). No default — sync errors if absent. |
174
+ | `jira-base-url` | **Yes** | — | Jira Cloud base URL (e.g. `https://icpipeline.atlassian.net`). |
175
+ | `jira-user-email` | **Yes** | — | Jira service account email for HTTP Basic auth. |
176
+ | `stories-dir` | No | `docs/stories` | Relative path to the stories directory. |
177
+ | `phase-regex` | No | `^phase\d+$` | Regex for phase directory matching. Empty string (`""`) disables phase labeling entirely. |
178
+ | `repo-url` | No | *(auto)* | Override repo URL. Normally derived from `GITHUB_SERVER_URL` + `GITHUB_REPOSITORY` (GHA-native). |
179
+
180
+ ### Per-tenant discovery
181
+
182
+ Sync discovers all tenant-specific Jira IDs at runtime via the REST API,
183
+ using canonical **names** as lookup keys. The names come from the framework's
184
+ shape definitions (`jira_shapes/*.yaml`) — the same spec the verifier
185
+ (`bootstrap-jira --verify`) enforces.
186
+
187
+ **Custom fields** (discovered via `GET /rest/api/3/field`):
188
+
189
+ | Name | Purpose |
190
+ |------|---------|
191
+ | `Requirement IDs` | Multi-value field for REQ-id traceability |
192
+ | `Story File` | URL to the canonical story `.md` file in the repo |
193
+ | `Agent Estimate` | Numeric estimate in agent-hours |
194
+
195
+ **Statuses** (discovered via `GET /rest/api/3/project/{key}/statuses`):
196
+
197
+ | Name | Category |
198
+ |------|----------|
199
+ | `To Do` | TODO |
200
+ | `Ready for AI agent` | TODO |
201
+ | `In Progress` | IN_PROGRESS |
202
+ | `In Review` | IN_PROGRESS |
203
+ | `Waiting` | IN_PROGRESS |
204
+ | `Blocked` | IN_PROGRESS |
205
+ | `Done` | DONE |
206
+ | `Won't do` | DONE |
207
+
208
+ **Transitions**: Sourced from the workflow spec (`jira_shapes/workflow.yaml`).
209
+
210
+ **Link types** (discovered via `GET /rest/api/3/issueLinkType`):
211
+ `Blocks` (inward: "is blocked by").
212
+
213
+ **Issue types** (discovered via `GET /rest/api/3/issuetype`):
214
+ `Story`, `Sub-task`.
215
+
216
+ If any required name is missing from the target project, sync fails fast:
217
+ ```
218
+ ERROR: Required custom field 'Story File' not found in Jira project METH;
219
+ run 'methodology-framework bootstrap-jira --verify --project-key=METH' to identify the gap.
220
+ ```
221
+
222
+ ### Story sync workflow
223
+
224
+ Syncs story `.md` files from the adopter's repo to Jira. Create
225
+ `.github/workflows/sync-stories.yml` in the adopter repo:
226
+
227
+ ```yaml
228
+ name: Sync stories to Jira
229
+
230
+ on:
231
+ push:
232
+ branches: [main]
233
+ paths:
234
+ - "docs/stories/**/*.md"
235
+ workflow_dispatch:
236
+ inputs:
237
+ mode:
238
+ description: "Sync mode"
239
+ required: true
240
+ default: "since-ref"
241
+ type: choice
242
+ options:
243
+ - since-ref
244
+ - all
245
+
246
+ jobs:
247
+ sync:
248
+ permissions:
249
+ contents: write
250
+ pull-requests: read
251
+ uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.3.0
252
+ with:
253
+ project_key: MYPROJ
254
+ mode: ${{ github.event.inputs.mode || 'since-ref' }}
255
+ jira_base_url: "https://myorg.atlassian.net"
256
+ jira_user_email: "devin-sync@myorg.atlassian.net"
257
+ # stories_dir: "docs/stories" # default
258
+ # phase_regex: "^phase\\d+$" # default; set "" to disable
259
+ secrets:
260
+ JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
261
+ ```
262
+
263
+ The `mode` input defaults to `since-ref` for routine push-triggered
264
+ runs (preserves the 250-story scale guard). Pass `mode: all` explicitly
265
+ for nightly or `workflow_dispatch` full-corpus syncs.
266
+
267
+ > **Secrets forwarding:** the caller's `secrets:` block must explicitly
268
+ > map each secret the reusable workflow declares. Secrets are NOT
269
+ > inherited by default in reusable workflows. Using `secrets: inherit`
270
+ > works but is brittle — prefer explicit mapping.
271
+
272
+ #### Required caller permissions
273
+
274
+ The reusable sync workflow declares `permissions: contents: write`
275
+ internally (for `jira_key` + `agent_acus` writeback commits). GitHub
276
+ Actions requires the **caller** to grant at least the same permissions
277
+ at the job level — otherwise the workflow fails at the `uses:`
278
+ resolution step with:
279
+
280
+ ```
281
+ The workflow is requesting 'contents: write', but is only allowed 'contents: read'.
282
+ ```
283
+
284
+ Add a job-level `permissions:` block to your caller:
285
+
286
+ ```yaml
287
+ jobs:
288
+ sync:
289
+ permissions:
290
+ contents: write
291
+ pull-requests: read
292
+ uses: whiteout59/methodology-framework/.github/workflows/sync.yml@v0.2.0
293
+ # ... with: / secrets: as above
294
+ ```
295
+
296
+ > **Job-scoped, not workflow-scoped.** Keep `permissions:` on the job,
297
+ > not the top-level `on:` — if future jobs are added to the same file,
298
+ > they should not inherit write access they don't need.
299
+
300
+ ### Playbook build + register workflow
301
+
302
+ Builds a concrete playbook from a parameterized body + bindings file,
303
+ then registers it with Devin's API. Create
304
+ `.github/workflows/build-and-register.yml` in the adopter repo:
305
+
306
+ ```yaml
307
+ name: Build and register playbook
308
+
309
+ on:
310
+ push:
311
+ branches: [main]
312
+ paths:
313
+ - "methodology/playbooks/*.body.md"
314
+ - "docs/jira-pickup-config.md"
315
+ workflow_dispatch:
316
+
317
+ jobs:
318
+ build-and-register:
319
+ uses: whiteout59/methodology-framework/.github/workflows/build-and-register.yml@v0.1.0
320
+ with:
321
+ playbook_body_path: "methodology/playbooks/router-base.body.md"
322
+ bindings_path: "docs/jira-pickup-config.md"
323
+ secrets:
324
+ DEVIN_API_TOKEN: ${{ secrets.DEVIN_API_TOKEN }}
325
+ ```
326
+
327
+ > **Nesting limit:** GitHub allows reusable workflow nesting up to
328
+ > 4 levels deep. If your repo wraps these workflows in another
329
+ > caller layer, verify the total nesting depth stays within limits.
330
+
331
+ ## How this repo evolves
332
+
333
+ The methodology-framework repo itself uses the same sync workflow it
334
+ ships to adopters. Framework-side evolution stories (new specs, CLI
335
+ features, workflow improvements) are drafted as `.md` files under
336
+ `docs/stories/` and synced to the **METH** Jira project via
337
+ [`.github/workflows/sync-stories.yml`](.github/workflows/sync-stories.yml).
338
+ Adopter-side stories (product-specific work) file in each adopter's own
339
+ Jira project (e.g. SCRUM for centralized-pipeline-ui).
340
+
341
+ ## PyPI Publishing
342
+
343
+ The package is published to PyPI automatically via OIDC Trusted Publishing when
344
+ `pyproject.toml` changes on `main`.
345
+
346
+ The `release.yml` workflow builds and publishes using
347
+ `pypa/gh-action-pypi-publish` in OIDC mode -- no `PYPI_API_TOKEN` secret
348
+ is needed. The job uses the `pypi` GitHub environment for protection rules.
349
+
350
+ **Operator setup (one-time):** bind the PyPI project `FlywheelIC`
351
+ to the GitHub repo `invoice-cloud/Flywheel`, workflow `release.yml`,
352
+ environment `pypi` at
353
+ [PyPI Trusted Publishers](https://pypi.org/manage/account/publishing/).
354
+
355
+ ## Release lifecycle
356
+
357
+ Version bumps in `pyproject.toml` drive the entire release cycle
358
+ automatically. The operator's only decision surface is PR review.
359
+
360
+ ### Primary flow
361
+
362
+ 1. A PR bumps the `version` field in `pyproject.toml` (e.g. `"0.2.0"` → `"0.2.1"`).
363
+ 2. Reviewer approves and merges the PR to `main`.
364
+ 3. `release.yml` fires on the `pyproject.toml` change, extracts the version,
365
+ validates it as stable SemVer (`^[0-9]+\.[0-9]+\.[0-9]+$`), builds the
366
+ package, creates an annotated tag such as `v0.2.1`, pushes the tag, and
367
+ publishes to PyPI via OIDC Trusted Publishing.
368
+
369
+ **Operator surface = PR review only.** No manual `git tag` step, no manual
370
+ publish trigger for the normal path.
371
+
372
+ ### Fallback (operator-pushed tag)
373
+
374
+ If the `pyproject.toml` push trigger does not fire, an operator can run
375
+ `release.yml` manually with the stable SemVer version input.
376
+
377
+ ### Pre-release versions
378
+
379
+ The `release.yml` workflow only tags **stable** SemVer versions. Versions
380
+ containing `-rc`, `-beta`, `-alpha`, `.dev`, or `.pre` suffixes (or any
381
+ string not matching `^[0-9]+\.[0-9]+\.[0-9]+$`) are logged and skipped.
382
+ Pre-release publishing is out of scope; if needed, a separate workflow
383
+ would handle pre-release tag patterns.
384
+
385
+ ## Cost tracking via `agent_acus`
386
+
387
+ The methodology's post-execution notes include an `agent_acus` field that
388
+ records per-story compute cost. Since agents cannot self-report ACU
389
+ consumption mid-session, a post-merge populator workflow backfills the
390
+ value from the Devin API after the session completes.
391
+
392
+ **Adopter wiring (3 steps):**
393
+
394
+ 1. Copy the template caller into your repo:
395
+ ```bash
396
+ cp "$(python -c "import methodology_framework; import pathlib; \
397
+ print(pathlib.Path(methodology_framework.__file__).parent / \
398
+ 'templates/github_workflows/populate-story-acus-caller.yml')")" \
399
+ .github/workflows/populate-story-acus.yml
400
+ ```
401
+ 2. Set the `DEVIN_API_TOKEN` repo secret (Settings > Secrets and
402
+ variables > Actions > New repository secret).
403
+ 3. Pin the framework version in the caller's `uses:` line.
404
+
405
+ The caller fires on merged PRs that touch story files, invokes the
406
+ reusable `populate-story-acus.yml` workflow, and opens a follow-on PR
407
+ with the populated ACU values.
408
+
409
+ ## Contributing
410
+
411
+ Framework evolution stories — new specs, CLI features, workflow improvements —
412
+ are filed as Markdown story files under [`docs/stories/`](docs/stories/).
413
+ See the [directory README](docs/stories/README.md) for the filing convention
414
+ (slug-named directories, frontmatter shape, `depends_on` hygiene). Stories
415
+ sync to the **METH** Jira project via the same sync workflow the framework
416
+ ships to adopters.
417
+
418
+ For the canonical story template and format spec, see
419
+ [`whiteout59/centralized-pipeline-ui/methodology/templates/story.md`](https://github.com/whiteout59/centralized-pipeline-ui/blob/main/methodology/templates/story.md)
420
+ and
421
+ [`whiteout59/centralized-pipeline-ui/methodology/devin-story-format.md`](https://github.com/whiteout59/centralized-pipeline-ui/blob/main/methodology/devin-story-format.md).
422
+
423
+ ### Jira Story Sync Auth
424
+
425
+ Story sync supports `JIRA_AUTH_MODE=basic` and `JIRA_AUTH_MODE=oauth`.
426
+
427
+ Basic mode is the default and requires `JIRA_BASE_URL`, `JIRA_USER_EMAIL`,
428
+ and `JIRA_API_TOKEN`.
429
+
430
+ OAuth mode requires `JIRA_CLOUD_ID` and either `JIRA_OAUTH_ACCESS_TOKEN` for
431
+ manual debugging or both `JIRA_OAUTH_CLIENT_ID` and `JIRA_OAUTH_CLIENT_SECRET`
432
+ for client-credentials token exchange via
433
+ `https://api.atlassian.com/oauth/token`. OAuth Jira REST calls use the
434
+ Atlassian API gateway base URL
435
+ `https://api.atlassian.com/ex/jira/{JIRA_CLOUD_ID}`.
436
+
437
+ ## Development
438
+
439
+ ```bash
440
+ pip install -e ".[dev]"
441
+ pytest tests/ -v
442
+ ruff check src/methodology_framework
443
+ ruff format --check src/methodology_framework
444
+ mypy --strict src/methodology_framework
445
+ ```
446
+
447
+ ### Shape-def expansion (v0.1.2+)
448
+
449
+ `workflow.yaml` now includes a `statusCategory` field on each status
450
+ (`TODO`, `IN_PROGRESS`, or `DONE`). The `--apply` handler validates
451
+ this field at load time and exits with a clear error if any status is
452
+ missing it.
453
+
454
+ `custom_fields.yaml` uses canonical shorthand types (`text`, `string`,
455
+ `multi-value`, `numeric`) that are mapped to fully-qualified Jira
456
+ identifiers at apply time. Fields may also specify a fully-qualified
457
+ type directly (any value containing `:` is passed through unchanged).
458
+
459
+ ### Recording new VCR cassettes
460
+
461
+ Integration tests under `tests/integration/` replay pre-recorded VCR
462
+ cassettes in CI (no network access required). To record fresh cassettes
463
+ against a real Jira instance:
464
+
465
+ ```bash
466
+ export JIRA_USER_EMAIL="<your-email>"
467
+ export JIRA_ADMIN_TOKEN="<your-api-token>"
468
+ PYTEST_VCR_MODE=record pytest tests/integration/ -v
469
+ ```
470
+
471
+ Cassettes are stored at `tests/fixtures/vcr/*.yaml`. The VCR config in
472
+ `tests/integration/conftest.py` automatically strips `Authorization`
473
+ headers from recorded interactions. **Never commit a cassette with a
474
+ real token in any header.**
475
+
476
+ After recording, verify no credentials leaked:
477
+
478
+ ```bash
479
+ grep -i 'authorization' tests/fixtures/vcr/*.yaml
480
+ # Expected: no output
481
+ ```
482
+
483
+ ## License
484
+
485
+ Apache-2.0