feat: GitHub-based JIRA YAML workflow#1553
Conversation
Proposes a system for managing JIRA tickets as versioned YAML files in .jira/, applied to JIRA automatically on PR merge via the existing build workflow. Includes specs and tasks for apply.mjs, fetch.mjs, /jira-propose skill, and build workflow integration.
|
- Workflow: loop runs apply→delete→fetch per changed file (not update-in-place)
- Use repo-{PR#} as remote link global ID for reliable JIRA indexing; no pagination needed
- XXXX files deleted and replaced by real-key files after apply succeeds
- fetch.mjs requires both key AND filename as required args
- Apply script exits 0 for no-updates files (triggering delete+fetch)
- Details section refreshed by build workflow via fetch, not read-only
- JIRA/changeset changes land in a single skip-ci commit
- Skill path: .claude/skills/jira-propose/SKILL.md (not commands/)
- Add quality gate: PRs must have a .jira/ YAML file
- Add PR comment step after apply listing updated tickets
- Propose description includes business value (customer names, elevator pitch)
- fetch can read any JIRA project; PDCL creation only restriction
- Resolved open questions; removed stale risks
jonsnyder
left a comment
There was a problem hiding this comment.
Submitting pending review to unblock replies.
Adds the full system for version-controlling JIRA changes as YAML files
in .jira/ and applying them automatically on merge to main.
- .jira/.gitkeep + README.md with schema docs, custom field reference,
and end-to-end workflow description
- scripts/jira/fetch.mjs: fetches JIRA issue state into details section
(two required args: ticket-key and filename; --dry-run supported)
- scripts/jira/apply.mjs: executes updates[] array against JIRA REST API,
creates remote link with globalId repo-{PR#} for idempotency, prints
resolved ticket key to stdout (--dry-run supported)
- .github/workflows/version-and-publish.yml: new apply-jira job runs on
every push to main; detects changed .jira/*.yml files, runs apply then
fetch on each, commits refreshed details, posts PR comment with ticket
links; publish job now depends on apply-jira with git pull --rebase
- .claude/skills/jira-propose/SKILL.md: skill for proposing ticket
changes locally from git context, no JIRA API calls
- CLAUDE.md: project guide with workflow, scripts, and custom field ref
- README.md: added JIRA workflow section
Task 6.2 (add JIRA_API_TOKEN to Production environment) requires
manual action in GitHub repository settings.
…d per-ticket Addresses four review comments on PR #1553: - api.mjs: extract all JIRA HTTP calls into a factory (createApi) with dryRun, baseUrl, and token injected — enables full unit testing without vi.mock() - apply.mjs / fetch.mjs: converted to exported functions (applyFile, fetchFile) that accept an injected api object; isMain guard added for CLI entry point - process.mjs: new script that orchestrates apply + fetch for a single file, handles XXXX→real-key rename, and short-circuits on missing file or empty updates — replaces 30+ lines of inline bash in the workflow - globalId: moved from auto-generated `repo-{PR#}` to a per-ticket unique hex string specified in the YAML remotelink entry, enabling multiple new tickets per PR merge - When a re-run finds an existing ticket via globalId search, the create fields are PUT to that ticket instead of creating a duplicate - apply.spec.js / fetch.spec.js / process.spec.js: new test files (78 tests passing) using injected mock APIs - jira-propose SKILL.md: updated template to include the remotelink entry with a unique generated globalId - workflow: bash per-file logic replaced with `node scripts/jira/process.mjs "$file"` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… imports
- Renamed api/apply/fetch/process from .mjs to .js to match repo convention
(package.json has "type": "module" so .js files are already ESM)
- Switched from default import to named imports for js-yaml v5 compatibility:
`import { load as yamlLoad, dump as yamlDump } from "js-yaml"`
- Added js-yaml as an explicit root devDependency (was previously a hoisted
transitive dep from read-yaml-file@js-yaml@3.x)
- Updated workflow and CLAUDE.md script references from .mjs to .js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved pnpm-lock.yaml merge conflict by regenerating via pnpm install. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…always required Addresses five review comments: - api.js: dryRun now only blocks mutation requests (non-GET); searchIssues and getRemoteLinks always execute so dry-run previews reflect real JIRA state - fetch.js: fetchFile always fetches from JIRA; in dryRun outputs the YAML content it would write to stdout instead of skipping the fetch entirely - fetch.js: removed existing-file check — fetch always overwrites with just the details section (updates have already been applied at this point) - process.js / apply.js / fetch.js: JIRA_API_TOKEN is now required even in dry-run mode since read queries need authentication - CLAUDE.md: removed (not ready to commit yet) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Satisfies ESLint func-style rule; removes unused beforeEach import from apply.spec.js; fixes no-await-in-loop in apply.js with Promise.all for parallel remote-link fetching and eslint-disable for sequential updates.
| import { fileURLToPath } from "url"; | ||
| import { load as yamlLoad } from "js-yaml"; | ||
| import createApi from "./api.js"; | ||
| import { JIRA_BASE_URL, JIRA_API_TOKEN } from "../team/config.js"; |
There was a problem hiding this comment.
Do not rely on team/config.js, and remove it locally too. Just make a config.js file in scripts/jira
There was a problem hiding this comment.
Done — created scripts/jira/config.js with just JIRA_BASE_URL and JIRA_API_TOKEN. All four jira scripts now import from ./config.js instead of ../team/config.js.
| @@ -0,0 +1,186 @@ | |||
| #!/usr/bin/env node | |||
There was a problem hiding this comment.
I want to make this script more dumb. Let's make these changes to the workflow and update all necessary files:
new tickets are not PDCL-XXXX-...yml. Instead of the XXXX they should have the global id (the short random string).
use the global id as a label on the new ticket.
The apply script should:
- extract the ticket or global id
- if global id check for existing ticket number
- Loop through all updates:
- if update is POST, run if no ticket number
- if update is PUT, run if have ticket number
- End loop
- Ensure there is a link from the ticket to the pr URL. (For every updated and new JIRA tickets). No need for there to be a remote link update in the yml.
There was a problem hiding this comment.
Redesigned as requested:
- Filename format: new tickets now use
PDCL-{globalId}-title.yml(e.g.PDCL-a3f8b2c1-title.yml) — the globalId is an 8-char hex string generated at propose time - Label-based idempotency: the globalId goes in
labelson the POST create body; apply searchesproject = PDCL AND labels = "{globalId}"to find existing tickets on re-run - Update loop simplified: POST to
/rest/api/2/issueruns only when no ticket key yet; all other updates run only when a ticket key is known - Auto remote link: apply always creates a remote link from the ticket to the PR at the end (idempotent via PR URL as globalId) — no remotelink entry needed in the YAML
- Skill updated: jira-propose template no longer includes a remotelink entry; instead it documents the globalId label convention
79 tests passing.
…lId in filename
- Add scripts/jira/config.js; remove team/config.js dependency from all jira scripts
- Rename new-ticket files from PDCL-XXXX-... to PDCL-{globalId}-... pattern
- Apply script now resolves new tickets by searching JIRA label matching the globalId
- Update loop: POST runs only when no ticket key yet; all others run when key is known
- Auto-create remote link to PR for every processed ticket (idempotent via PR URL)
- Remove explicit remotelink entry from jira-propose skill template
- Update openspec tasks/design/proposal to reflect as-built implementation
Summary
openspec/changes/github-jira-yaml-workflow/) proposing a system for managing JIRA tickets as versioned YAML files in.jira/detailssnapshot and anupdatesarray of idempotent REST calls applied on PR mergescripts/jira/apply.mjs <filename>applies updates and prints the resolved ticket key to stdout;scripts/jira/fetch.mjs <key> <filename>refreshes thedetailssection from live JIRA dataversion-and-publish.ymlbuild workflow gains anapply-jirajob that detects changed.jira/*.ymlfiles, runs apply → fetch on each, and commits refreshed details back to main/jira-proposeClaude Code skill creates or updates.jira/files locally from git context — no JIRA API calls made locallyArtifacts
proposal.mddesign.mdspecs/jira-yaml-schemadetails/updatesstructure,setidempotencyspecs/jira-apply-workflowspecs/jira-fetch-scriptspecs/jira-propose-actiontasks.mdTest plan
/opsx:applyto begin implementation when ready🤖 Generated with Claude Code