This file is the single source of truth for AI coding assistants. It is read by GitHub Copilot, Cursor, Claude Code, and other AI tools. Tool-specific files (e.g.,
CLAUDE.md,.cursor/rules/) reference this file.
This repository supports multiple AI coding tools without vendor lock-in:
| Tool | Entry Point | References |
|---|---|---|
| GitHub Copilot | .github/copilot-instructions.md |
→ AGENTS.md, ai-context/ |
| Cursor | .cursor/rules/*.mdc |
→ AGENTS.md, ai-context/ |
| Claude Code | CLAUDE.md |
→ AGENTS.md, ai-context/ |
| Any tool | AGENTS.md (this file) |
Canonical project rules |
AGENTS.md # Project-level rules (this file)
CLAUDE.md # Claude Code entry point
.cursor/rules/jiraps.mdc # Cursor entry point
.github/
├── copilot-instructions.md # Copilot entry point
├── ai-context/ # Shared context (tool-agnostic)
│ └── powershell-rules.md # PowerShell-specific rules
└── instructions/ # Copilot file-pattern rules
└── *.instructions.md # Applied by file glob
| To change… | Edit this file |
|---|---|
| Project-wide rules | AGENTS.md |
| PowerShell-specific rules | .github/ai-context/powershell-rules.md |
| Tool entry points | Only if adding quick-reference summaries |
RULE: A commit is a complete, shippable unit of work. It includes the code, the tests, and the documentation — all passing. Incomplete work is not committable.
Every commit must include all of the following:
| Component | Required | Location |
|---|---|---|
| Code | The implementation | JiraPS/Public/ or JiraPS/Private/ |
| Unit Tests | Tests that verify the code works | Tests/Functions/Public/ or Tests/Functions/Private/ |
| Green Tests | All tests passing | Run Invoke-Build -Task Build, Test |
| Documentation | Updated docs for user-facing changes | docs/en-US/commands/*.md, CHANGELOG.md |
| Linter Clean | No new PSScriptAnalyzer errors | Run PSScriptAnalyzer |
JiraPS is a mature PowerShell module (v2.15) that provides a comprehensive interface to interact with Atlassian JIRA via REST API. This is a legacy codebase that requires modernization while maintaining backward compatibility for its substantial user base.
mastervX.Y.Z to trigger release)JiraPS targets both Jira Cloud AND Data Center. These have different APIs (accountId vs username, ADF vs plain text, different search and pagination). Changes must work on both. See
.github/ai-context/powershell-rules.mdfor detailed rules.
JiraPS/
├── JiraPS/ # Module source
│ ├── Public/ # 58 exported cmdlets (Get-JiraIssue, New-JiraSession, etc.)
│ ├── Private/ # Internal functions (ConvertTo-*, Resolve-*, Invoke-WebRequest wrapper)
│ ├── JiraPS.psm1 # Main module file (loads and exports functions)
│ └── JiraPS.psd1 # Module manifest
├── Tests/ # Pester test suite
│ ├── Functions/
│ │ ├── Public/ # Tests for public CRUD functions
│ │ └── Private/ # Tests for private converter functions
│ └── README.md # Comprehensive testing guide
├── Tools/ # Build automation
│ ├── BuildTools.psm1
│ └── build.requirements.psd1
├── docs/ # Documentation (Markdown for PlatyPS)
└── JiraPS.build.ps1 # Build script (InvokeBuild)
REST API Wrapper: All HTTP calls go through Invoke-JiraMethod (in Public/)
Type Conversion: Private ConvertTo-* functions transform API responses
ConvertTo-JiraIssue: Main issue converterConvertTo-JiraUser, ConvertTo-JiraProject, etc.-InputObject, return custom PSObject with type nameSession Management:
New-JiraSession: Creates authenticated sessionSet-JiraConfigServer: Configures server URL (stored in AppData)Module Loading (JiraPS.psm1):
Get-, Set-, New-, Remove-, etc.).SYNOPSIS, .DESCRIPTION, .EXAMPLE, .LINK or external help XMLdocs/en-US/commands/[CmdletBinding()] and parameter validation attributes-ErrorAction parameter support and meaningful error messages[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[String]$IssueKey,
[PSCredential]
[System.Management.Automation.Credential()]
$Credential = [System.Management.Automation.PSCredential]::Empty
)
Tests/Functions/Public/<FunctionName>.Unit.Tests.ps1Tests/Functions/Private/<FunctionName>.Unit.Tests.ps1.template.ps1 file
Tests/Functions/Public/.template.ps1 - for API-calling CRUD functionsTests/Functions/Private/.template.ps1 - for data transformation/converter functionsTests/README.md for comprehensive testing documentation including:
IMPORTANT: Tests run against the built module in
Release/, not the source files. You MUST build before testing. RunningInvoke-Pesterdirectly will not work.
Standard Workflow:
./Tools/setup.ps1 # First time: install dependencies
Invoke-Build -Task Build, Test # Build and test in one command
Build Tasks (via Invoke-Build):
| Task | What it does |
|---|---|
Clean |
Removes Release/ and test artifacts |
Build |
Compiles module into Release/ (runs Clean first) |
Test |
Runs Pester tests against built module |
GenerateExternalHelp |
Generates help XML from docs/ markdown |
Publish |
Publishes to PowerShell Gallery (release tags only) |
Common Mistakes:
Invoke-Pester directly — Tests expect the compiled module in Release/Invoke-Build -Task Test without building — Uses stale or missing code./Tools/setup.ps1 — Missing Pester, InvokeBuild, etc.Invoke-Build -Task Build, Test — Correct: build then test/rest/api/2/.../rest/api/2/issue/{issueIdOrKey} - Get/Update issue/rest/api/2/search - JQL search (GET with query params, or POST with JSON body)/rest/api/2/project - Project operations/rest/api/2/user - User management/rest/api/2/issue/{issueIdOrKey}/comment - Comments/rest/api/2/issue/{issueIdOrKey}/worklog - Work logsNew-JiraSession (creates WebSession)# JIRA uses startAt/maxResults pattern (Data Center and Cloud v2)
Invoke-JiraMethod -URI $uri -Paging
# Automatically handles multiple pages
CRITICAL CONTEXT: JiraPS targets both Jira Cloud and Jira Data Center. These are different products with different API behaviors. Any change to API endpoints, request bodies, or response handling must consider both deployment types. Never assume Cloud-only or DC-only usage.
| Aspect | Jira Cloud | Jira Data Center |
|---|---|---|
| API versions | v2 (deprecated) → v3 (current) | v2 (stable), v3 (partial, version-dependent) |
| User identity | accountId (GDPR, username/name removed) |
username / name (traditional) |
| Text fields | Atlassian Document Format (ADF) JSON in v3 | Plain strings or wiki markup |
| Search | POST /rest/api/3/search/jql (Cloud migration) |
GET /rest/api/2/search (stable) |
| Pagination | nextPageToken (Cloud search v3) |
startAt / maxResults (offset-based) |
| Rate limiting | Enforced (HTTP 429 + Retry-After) |
Typically not enforced |
| Session auth | Deprecated | Supported |
On Cloud, Atlassian removed username/name fields under GDPR. User operations
require accountId:
# Cloud v3: use accountId
$resourceUri = "$server/rest/api/3/user?accountId={0}"
$body = @{ assignee = @{ accountId = $user.AccountId } }
# Data Center: use username/name
$resourceUri = "$server/rest/api/2/user?username={0}"
$body = @{ assignee = @{ name = $user.Name } }
Currently affected functions (still DC-centric, need Cloud paths):
Get-JiraUser, Set-JiraUser, Remove-JiraUser, New-JiraIssue (reporter),
Set-JiraIssue (assignee), Invoke-JiraIssueTransition, Add-JiraGroupMember,
Remove-JiraGroupMember, Add-JiraIssueWatcher, Remove-JiraIssueWatcher,
Resolve-JiraUser, ConvertTo-JiraUser.ToString()
Cloud v3 returns and expects rich-text fields (description, comment.body, etc.)
as ADF JSON objects. Data Center returns these as plain strings.
# Cloud v3 response for description:
# { "type": "doc", "version": 1, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Hello" }] }] }
# Data Center response for description:
# "Hello"
Rule: ADF conversion (ConvertTo-AtlassianDocumentFormat / ConvertFrom-AtlassianDocumentFormat)
must only be applied when targeting Cloud v3. Sending ADF to Data Center will produce
garbled content or API errors. Reading plain strings through ADF conversion is wasteful
(though the current ConvertFrom- function handles strings gracefully as a fallback).
| Deployment | Endpoint | Method | Pagination |
|---|---|---|---|
| Cloud v3 | /rest/api/3/search/jql |
POST (JSON body) | nextPageToken |
| Data Center | /rest/api/2/search |
GET (query params) | startAt / maxResults |
These are not interchangeable. The Cloud endpoint may not exist on DC, and the paging models are fundamentally different.
Get-JiraServerInformation returns deploymentType from the API (Cloud or Server).
This is available via ConvertTo-JiraServerInfo but not currently used for branching.
Planned approach: After Set-JiraConfigServer, detect and cache the deployment type,
then use it to select the correct API version, user identity model, text format, search
endpoint, and pagination strategy.
When reviewing PRs that modify API endpoints or request/response handling, always check:
/search/jql,
accountId), is there a DC fallback?username → accountId for Cloud, keep username for DC)JiraPS/Public/Verb-JiraNoun.ps1Tests/Functions/Public/Verb-JiraNoun.Unit.Tests.ps1 (use .template.ps1)docs/en-US/commands/Verb-JiraNoun.mdfunction Get-JiraExample {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[String]$Id,
[PSCredential]$Credential
)
begin {
Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started"
}
process {
$parameter = @{
URI = "$($script:JiraServerUrl)/rest/api/2/example/$Id"
Method = "GET"
}
if ($Credential) {
$parameter["Credential"] = $Credential
}
$result = Invoke-JiraMethod @parameter
ConvertTo-JiraExample -InputObject $result
}
end {
Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete"
}
}
Invoke-Build -Task Build, Test
When JIRA API changes affect a cmdlet:
Invoke-JiraMethod callaccountId for Cloud, username for DC)ConvertTo-*) if response schema changed## [NEXT VERSION]JiraPS uses two primary test templates based on function type. For comprehensive details, see Tests/README.md.
Location: Tests/Functions/Public/
Template: Tests/Functions/Public/.template.ps1
Reference: Add-JiraFilterPermission.Unit.Tests.ps1
Use For: Get-_, Set-_, New-_, Remove-_, Add-* functions that make API calls
Key Characteristics: Three Describe blocks (Signature, Behavior, Input Validation), extensive mocking, API interaction focus
Location: Tests/Functions/Private/
Template: Tests/Functions/Private/.template.ps1
Reference: ConvertTo-JiraAttachment.Unit.Tests.ps1
Use For: ConvertTo-_, ConvertFrom-_ functions that transform data
Key Characteristics: Single Describe block with four contexts (Object Conversion, Property Mapping, Type Conversion, Pipeline Support), large JSON fixtures, minimal mocking
Enable mock parameter debugging in tests:
BeforeAll {
. "$PSScriptRoot/../Helpers/Write-MockDebugInfo.ps1"
$VerbosePreference = 'Continue' # Uncomment to see mock debug output
Mock Invoke-JiraMethod -ModuleName JiraPS {
Write-MockDebugInfo 'Invoke-JiraMethod' 'Method', 'Uri', 'Body'
# mock implementation
}
}
Output format:
🔷 Mock: Invoke-JiraMethod
[Method] = "GET"
[Uri] = "https://jira.example.com/rest/api/2/issue/TEST-123"
[Body] = <null>
Note: The -Verbose parameter on Invoke-Pester does NOT enable mock debugging. You must set $VerbosePreference = 'Continue' inside the test file’s BeforeAll block.
JIRA custom fields have IDs like customfield_10001:
# Set custom field in New-JiraIssue or Set-JiraIssue
$fields = @{
customfield_10001 = "Value"
}
New-JiraIssue -Fields $fields ...
build_and_test.yml: Runs on PR/push to master
release.yml: Runs on version tags (v*)
Invoke-Build -Task PublishRuntime: None (pure PowerShell)
Build-time (in Tools/build.requirements.psd1):
docs/en-US/commands/*.mdJiraPS/en-US/JiraPS-help.xml (via PlatyPS)docs/en-US/about_*.md → compiled to JiraPS/en-US/*.help.txtInvoke-JiraMethod → ConvertTo-*)Write-Verbose for debugging outputaccountId for Cloud and username/name for Data Center user operationsInvoke-JiraMethod for REST calls$script:JiraServerUrl)username query params against Cloud v3 endpoints (use accountId)ConvertTo-URLEncoded for query parametersstartAt/maxResults, not skip/taketry {
$result = Invoke-JiraMethod @params
} catch {
$PSCmdlet.ThrowTerminatingError($_)
}
# Use validation attributes
[ValidateNotNullOrEmpty()]
[ValidatePattern('^\w+-\d+$')] # For issue keys like "PROJ-123"
[ValidateScript({ Test-Path $_ })]
[Parameter(ValueFromPipeline)]
[Object[]]$InputObject
process {
foreach ($item in $InputObject) {
# Process each item
}
}
$result.PSObject.TypeNames.Insert(0, 'JiraPS.Issue')
# Enables custom formatting via JiraPS.format.ps1xml
High Priority:
Medium Priority:
Low Priority:
# 1. Setup
git checkout master && git pull origin master
git checkout -b feature/my-enhancement
./Tools/setup.ps1
# 2. Implement ONE functionality (code + tests + docs together)
# - Write/modify code in JiraPS/Public/ or JiraPS/Private/
# - Write/update tests in Tests/Functions/
# - Update docs in docs/en-US/commands/ and CHANGELOG.md
# 3. Verify before commit (all must pass)
Invoke-Build -Task Build, Test # ⛔ Red tests = not committable
Invoke-Build -Task GenerateExternalHelp
# 4. Commit the complete functionality
git add .
git commit -m "Add: Description of change"
# 5. Repeat steps 2-4 for each additional functionality
# 6. Push and create PR
git push origin feature/my-enhancement
JiraPS is a well-established PowerShell module with a large user base. When contributing:
This is a legacy codebase requiring careful, incremental improvements rather than aggressive refactoring.