hardis
post-comment
Description
Command Behavior
Posts a markdown comment on one or several tickets of JIRA, Azure Boards, ServiceNow or Aha!, converted to the format of each ticketing system.
An AI agent can report what it designed or tested on the ticket it worked on, a CI job can tell what it did, and a developer can comment without opening the ticketing system. The deployment comments sfdx-hardis posts after a deployment go through the same code.
- Several tickets: repeat
--id(or separate the identifiers with commas). Each ticket is handled on its own: one failing does not stop the others. - Markdown in, native format out: headings, bold, italic, links, lists, tables and code blocks become an Atlassian document on Jira Cloud, wiki markup on Jira Server / Data Center, HTML on Azure Boards and Aha!, and plain text with the URLs spelled out in a ServiceNow journal.
- Update instead of adding (
--marker): a rerun with the same marker replaces the comment it posted before, so a CI job can keep one status comment up to date. ServiceNow journal notes cannot be edited: a new note is added there, with a warning. - Report: a table and a CSV file with the action (created, updated, skipped, refused or failed) and the link of each comment. Exit code 0 when no ticket failed or was refused, 2 when some did (or a status or points step failed), 1 when all did. A skipped ticket is not a failure.
Qualifying a ticket in the same run
Options meant for an agent that reviews a backlog. Each ticket goes through the same steps, in this order:
- Checks, before any write:
--allowed-projects a,bleaves a ticket of another project untouched (actionrefused,errorName: TicketOutOfScope).--skip-if-markedleaves untouched a ticket that carries--marker-label(default--label), or a comment of the authenticated account starting with--prefix(actionskipped,errorName: TicketAlreadyMarked): it is neither commented nor updated, even with--marker. Comments of other accounts never count. - Comment, with
--prefixas its first line (never twice),--mention-assigneeadding a real mention of the assignee read on the ticket (never taken from the text; nothing when unassigned), and--visibility role:<role>orgroup:<group>restricting it to the members of a role or a group. A ticketing system that cannot restrict comments refuses--visibilitywithTicketProviderNotSupported, before anything is written. - Label (
--label), added on top of the existing ones. If it cannot be added, the result isfailedalthough the comment is saved: add the label only, do not post again. - Status (
--transition): the target status, by name, untranslated name or id, never the name of the transition. - Story points (
--points): a filled value is kept unless--overwrite-points.--points-fieldnames the field; a ticketing system that cannot find it alone refuses--pointswithout it, before anything is written. A ticket type without the field isskipped, not an error.
Each ticketing system checks these options before the first write on each ticket: a label it cannot store, a missing field or a restriction it cannot apply fail the ticket with nothing written.
--strict-markdown keeps only headings, bullet and numbered lists, paragraphs, bold and inline code: links, images, HTML, code blocks, tables and mentions are posted as literal text, so text quoted from a ticket cannot inject anything. Without it, the full markdown is converted as usual.
A failed status or points step does not undo the comment. Each result carries label, transition and points: {status: done | skipped | failed | not-requested, value, field, reason, error: {name, message}}. A failed step names its error: TicketTransitionInvalid (status unknown for the type of the ticket, or move refused by the workflow), TicketPointsInvalid (no --points-field where it is required, or value refused). A failed status or points step makes the exit code 2, and is counted in stepsFailed.
Checking the result: hardisget returns the comments as flattened plain text, without their HTML or Atlassian formatting: it tells what was posted, not how it renders. Check the rendering (headings, lists, mention, visibility) in the ticketing system itself.
JIRA Cloud or Server: JIRA Cloud is recognized by its host (atlassian.net or .jira.com). A Cloud site reached through a custom domain is taken for Server / Data Center, and gets wiki markup instead of an Atlassian document.
Provider detection
The ticketing system is deduced from the shape of the identifier, as hardisget does, so
--provider is only needed to disambiguate:
| Identifier | Provider | Example |
|---|---|---|
PROJECT-123 |
JIRA, or Aha! when ticketingProvider is AHA |
--id ACME-4567 |
1234 or AB-1234 |
Azure Boards | --id AB-4567 |
INC0012345, CHG..., RITM..., DMND... |
ServiceNow | --id INC0012345 |
Configuration
The ticketing variables sfdx-hardis already documents, from CI/CD variables or a local .env file:
- JIRA:
JIRA_HOST+ (JIRA_EMAIL+JIRA_TOKEN) orJIRA_PATor (JIRA_CLIENT_ID+JIRA_CLIENT_SECRET) - Azure Boards: a token (
CI_SFDX_HARDIS_AZURE_TOKEN,SYSTEM_ACCESSTOKENorAZURE_DEVOPS_EXT_PAT). The organization and the project are read from the git remote, or fromSYSTEM_COLLECTIONURIandSYSTEM_TEAMPROJECT. - ServiceNow:
SERVICENOW_URL+SERVICENOW_USERNAME+SERVICENOW_PASSWORD. The note goes towork_notesby default:--comment-field(formerly--servicenow-field, still accepted), thenSERVICENOW_COMMENT_FIELD, then theserviceNowCommentFieldconfiguration choose another journal field, such ascomments. - Aha!:
AHA_HOST+AHA_API_KEY
Technical explanations
- One posting path: every connector implements
postComment(ticketId, markdown, options), and its deployment comments call it too.TicketProvider.postComment()picks the connector with the same routing asTicketProvider.getTicketDetails(). - Conversion: the markdown is read with
marked. Azure Boards and Aha! get its sanitized HTML. The Atlassian document, the wiki markup and the plain text are built from the same tokens, so a comment reads the same everywhere. - Marker:
sfdx-hardis:<marker>is written in the comment, hidden in the title of an empty element on Azure Boards and Aha! (Azure Boards strips HTML comments), as a last line in code format on JIRA and ServiceNow, which have no hidden text. A rerun reads the comments of the ticket and updates the most recent one holding the marker. A marker holds letters, digits,_,.,:and-, 80 characters at most. - Qualification options: before the first write on a ticket, the command calls
validateQualifyOptions()on its provider; each ticketing system applies its own rules in its class. JIRA: labels without spaces,--pointsneeds--points-field customfield_NNNNN,--visibilitybecomes thevisibilityof the comment. Azure Boards:--points-fieldis optional, the field is found on the work item type (StoryPoints, then Effort, then Size);--visibilityis refused; with--strict-markdownthe headings keep their markdown level (#gives<h1>), with an empty line before each one but the first. ServiceNow: labels are tags, the status is astatechoice of the table, points go tostory_points(or--points-field), the assignee is named as text,--visibilityis refused. Aha!: labels are tags, the status is a workflow status, points go tooriginal_estimate(or a custom field named by--points-field), the assignee is named as text,--visibilityis refused. - Proxy support: the HTTP calls go through the shared proxy-aware client (
HTTP_PROXY,HTTPS_PROXY,NO_PROXY).
Agent Mode
Use --agent to disable all interactive prompts. --id and --message (or --file) are then required.
sf hardis:ticket:post-comment --id ACME-4567 --file docs/ACME-4567/design.md --marker design --agent --json
The same applies in CI.
Parameters
| Name | Type | Description | Default | Required | Options |
|---|---|---|---|---|---|
| agent | boolean | Run in non-interactive mode for agents and automation | |||
| allowed-projects | option | Comma-separated projects the tickets must belong to: any other one is refused (action refused) and left untouched | |||
| comment-field | option | Field the comment is written to, for a ticketing system that keeps comments in several fields. Ignored by the others | |||
| debug -d |
boolean | Activate debug mode (more logs) | |||
| file -f |
option | Markdown file holding the comment to post, instead of --message | |||
| flags-dir | option | Import flag values from a directory. | |||
| id -i |
option | Ticket to comment on: JIRA key or Aha! feature (ACME-123), Azure Boards work item (1234 or AB-1234) or ServiceNow number (INC0012345). Repeat it, or separate the identifiers with commas | |||
| json | boolean | Format output as json. | |||
| label | option | Label added to the ticket once the comment is posted | |||
| marker | option | Key of the comment: a rerun with the same marker updates the comment it posted before instead of adding one | |||
| marker-label | option | Label checked by --skip-if-marked. Defaults to --label | |||
| mention-assignee | boolean | Mention the assignee read on the ticket, under the first line of the comment | |||
| message -m |
option | Comment to post, in markdown | |||
| overwrite-points | boolean | Replace story points that are already filled | |||
| points | option | Story points written once the comment is posted. A filled value is kept unless --overwrite-points | |||
| points-field | option | Field the story points are written to, as the ticketing system names it. Some ticketing systems require it with --points | |||
| prefix | option | First line of the comment, added unless the text already starts with it | |||
| provider -p |
option | Force the ticketing system instead of deducing it from the identifier | jira azure servicenow aha |
||
| skip-if-marked | boolean | Leave a ticket untouched (action skipped) when it carries --marker-label or a comment of this account starting with --prefix | |||
| skipauth | boolean | Skip authentication check when a default username is required | |||
| strict-markdown | boolean | Keep only headings, lists, paragraphs, bold and code; every other construct (link, image, HTML, mention...) is posted as literal text | |||
| transition | option | Status the ticket is moved to once the comment is posted (name, untranslated name or id) | |||
| visibility | option | role: |
|||
| websocket | option | Websocket host:port for VsCode SFDX Hardis UI integration |
Examples
$ sf hardis:ticket:post-comment --id ACME-4567 --message "The fix is deployed in UAT, please test it"
$ sf hardis:ticket:post-comment --id ACME-4567 --id ACME-4568 --file release-notes.md
$ sf hardis:ticket:post-comment --id ACME-4567 --file docs/ACME-4567/design.md --marker design --agent
$ sf hardis:ticket:post-comment --id ACME-4567 --file draft.md --prefix "Automatic qualification" --skip-if-marked --label ia-qualified --transition "Ready for dev" --points 3 --points-field customfield_10016 --mention-assignee --visibility role:Developers --allowed-projects ACME --strict-markdown --agent --json
$ sf hardis:ticket:post-comment --id AB-1234 --file test-report.md --marker test-report
$ sf hardis:ticket:post-comment --id AB-1234 --file draft.md --prefix "Automatic qualification" --skip-if-marked --label ia-qualified --transition Active --points 3 --mention-assignee --allowed-projects "My Project" --agent --json
$ sf hardis:ticket:post-comment --id INC0012345 --message "Fixed in the last release" --comment-field comments
$ sf hardis:ticket:post-comment --id STRY0010001 --file draft.md --prefix "Automatic qualification" --skip-if-marked --label ia-qualified --transition "Work in progress" --points 5 --mention-assignee --agent --json
$ sf hardis:ticket:post-comment --id PROD-12 --provider aha --file draft.md --prefix "Automatic qualification" --skip-if-marked --label ia-qualified --transition "Ready to develop" --points 3 --mention-assignee --agent --json