Skip to content

feat(unic-ticket-specification): add portable ticket-specification workflow plugin#257

Open
PetersSourceCode wants to merge 2 commits into
developfrom
feature/add-ticket-specification-workflow
Open

feat(unic-ticket-specification): add portable ticket-specification workflow plugin#257
PetersSourceCode wants to merge 2 commits into
developfrom
feature/add-ticket-specification-workflow

Conversation

@PetersSourceCode

Copy link
Copy Markdown
Collaborator

What

Adds unic-ticket-specification — a Claude Code plugin packaging a portable Archon workflow that takes a tracker ticket from intake to "ready for implementation". Tracker-agnostic (Jira / Azure DevOps / GitHub), multi-repo, and OS-independent (Windows / macOS).

Tracking issue: #256 · Feature PRD: docs/issues/unic-ticket-specification/PRD.md

Workflow

11-node DAG: detect-input → analyze → classify → rewrite (Bug | CR-Story) → assess-completeness (non-blocking) → estimate (PERT) → persist-local → present-draft → approval-gate ✓ → apply (create | update) → report. Nothing is written to the tracker before the human approval gate.

Contents

  • Bundle (.archon/): workflow DAG, seven uts-* command templates, tracker MCP config, documented ticket-spec.config.example.yaml, bundle README
  • /unic-ticket-specification:setup: zero-config conversational install/config command (ships no JS)
  • Plugin scaffolding: .claude-plugin/ manifests, package.json, CONTEXT.md, AGENTS.md, CLAUDE.md symlink, README.md, CHANGELOG.md
  • Four plugin ADRs (docs/adr/): tool-agnostic config-driven, MCP-first/CLI-fallback, Markdown-only descriptions, setup-as-conversational-command
  • Registered in the root marketplace; indexed in root AGENTS.md + CONTEXT-MAP.md (app:unic-ticket-specification)

Design notes

  • No tracker/tenant/repo/OS detail is hardcoded — it all lives in the per-project config (ADR-0001). Only the config template ships; the active ticket-spec.config.yaml is created per project.
  • Example wording in uts-rewrite-crstory.md was genericised (no client-specific names).
  • No LICENSE file added (maintainer manages those by hand per monorepo convention).

Validation

  • pnpm --filter unic-ticket-specification verify:changelog passes
  • Prettier (Markdown) + Biome (JSON) clean on all added/changed files
  • This plugin ships no JS, so it has no test/typecheck script

Notes for reviewer

🤖 Generated with Claude Code

PetersSourceCode and others added 2 commits June 18, 2026 15:45
…rkflow plugin

Package a tracker-agnostic Archon workflow that takes a ticket from intake to
"ready for implementation": detect input -> analyze across configured repos +
linked docs -> classify Bug vs CR/Story -> rewrite to template -> non-blocking
completeness check -> PERT estimate -> human approval gate -> apply (create or
update) -> report. All tracker/tenant/repo/OS variability lives in a per-project
config; nothing tracker-specific is in the workflow or command templates.

- 11-node workflow DAG + 7 uts-* command templates + tracker MCP config + config template
- /unic-ticket-specification:setup zero-config slash command (no JS lib)
- four plugin ADRs under docs/adr/
- registered in the root marketplace; indexed in AGENTS.md + CONTEXT-MAP.md
- Feature record at docs/issues/unic-ticket-specification/PRD.md
- example wording in uts-rewrite-crstory genericised (no client-specific names)

Refs #256

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@PetersSourceCode PetersSourceCode requested a review from orioltf June 19, 2026 07:28
@orioltf

orioltf commented Jun 22, 2026

Copy link
Copy Markdown
Member

Added a generic comment to the referenced issue: #256 (comment)

@orioltf orioltf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: description written as raw Markdown into non-Markdown tracker fields

Found while running this workflow against a real Azure DevOps project (FZAG/dxp). The apply steps instruct the agent to write "the full contents of draft-description.md" (which is Markdown) verbatim into the tracker's description field. That only happens to be correct for GitHub (Markdown-native). For Azure DevOps and Jira the description field is not Markdown, so the raw #, **, |, - [ ] render as literal characters — an unreadable ticket for humans.

Evidence (two runs, same workflow)

  • Run A — the apply step happened to convert MD→HTML before the write → readable ticket.
  • Run B (resumed run) — followed the instruction literally, wrote raw Markdown into Azure DevOps System.Description → wall of literal ##/**/| text.

The difference was pure model discretion: the spec never requires conversion, so determinism is left to chance.

Root cause

The description format is per-tracker, but the commands treat all three trackers as "paste Markdown":

Tracker Description field Correct input
Azure DevOps System.Description / Microsoft.VSTS.TCM.ReproStepsHTML render MD → HTML
GitHub issue body — Markdown-native verbatim ✅ (current behaviour fine)
Jira ADF / wiki markup render, not raw MD

Suggested fix

Keep draft-description.md as Markdown (it's what the human reviews and the local artifact stores), and convert at apply time, tracker-aware. Add a shared ## Description formatting section to both uts-apply-create.md and uts-apply-update.md:

## Description formatting — render to the tracker's native format

`draft-description.md` is Markdown. Render it to the tracker's format before writing —
never paste raw Markdown into a field that is not Markdown-native.

- GitHub      — issue body is Markdown-native. Use draft-description.md verbatim.
- Azure DevOps — System.Description / ReproSteps are HTML fields. Convert MD → HTML
                 (headings → <h2>/<h3>, **bold** → <strong>, lists → <ul>/<li>,
                 tables → <table>, fenced code → <pre><code>, links → <a>,
                 task items - [ ] → checkboxes). Write to $ARTIFACTS_DIR/description.html
                 (inspectable + deterministic), then set the field from that file.
- Jira        — description must be ADF / wiki markup, not raw Markdown. Convert before edit.

Then change each tracker's apply step to write the formatted output (HTML for ADO, ADF/wiki for Jira) instead of "full contents of draft-description.md". See the two inline comments for the exact lines.

Writing the HTML to description.html first makes the conversion deterministic-by-instruction rather than by luck, and keeps it inspectable in the run artifacts.

(Filed from a downstream install of this bundle; I've already verified the fix end-to-end — npx marked --gfm produces clean ADO-compatible HTML, and re-applying it to the affected work item flipped multilineFieldsFormat.System.Description to html and rendered correctly.)

### azure-devops

1. Update the work item `key` in `tracker.azure_devops.org_url` / `project`,
setting the description/repro field to the full contents of

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Azure DevOps System.Description (and Microsoft.VSTS.TCM.ReproSteps) are HTML fields. Writing the raw Markdown contents of draft-description.md here makes ##, **, | tables and - [ ] render as literal text — the ticket is unreadable to humans. This is exactly what happened on a real resumed run.

Suggest: render draft-description.md → HTML first (e.g. write to $ARTIFACTS_DIR/description.html), then set the field from that, per a shared ## Description formatting section. e.g.

1. Render `draft-description.md` to HTML per *Description formatting* and save to
   `$ARTIFACTS_DIR/description.html`. Update the work item `key`, setting the
   description/repro field to that HTML — never the raw Markdown.

GitHub (line 52) is fine as-is since the issue body is Markdown-native; Jira (line 36) should use ADF/wiki markup, also not raw Markdown.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, diff generated in my own project installation trying to solve this, in case this can help here too:

Image

1. Work-item type = `tracker.azure_devops.work_item_types.bug` when kind=BUG,
else `...work_item_types.cr_story`.
2. Create the work item in `tracker.azure_devops.org_url` / `project` with the
title and the description = full contents of `draft-description.md`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same bug on the create path. Azure DevOps System.Description is an HTML field; the raw Markdown from draft-description.md renders as literal #/**/| characters.

Suggest rendering MD → HTML before the write:

2. Render `draft-description.md` to HTML per *Description formatting* and save to
   `$ARTIFACTS_DIR/description.html`. Create the work item with the title, then set
   the description/repro field to that HTML — never the raw Markdown.

Mirror of the uts-apply-update.md comment. GitHub (line 67) is correct as Markdown; Jira (line 46) should be ADF/wiki markup.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, diff generated in my own project installation trying to solve this, in case this can help here too:

Image

@orioltf orioltf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: default the Azure DevOps MCP server to --authentication azcli

Found while running this workflow repeatedly against an Azure DevOps project (FZAG/dxp): @azure-devops/mcp defaults to interactive OAuth, so it pops a browser login on (re)spawn — which happens on every workflow run, making back-to-back ticket runs painful.

The server supports reusing an existing az login session via the --authentication azcli flag (per the Microsoft azure-devops-mcp docs). Switching to it means the user logs in once and every spawned MCP server piggybacks on the cached Azure CLI token — no per-run browser prompt.

Recommended generated config for the azure-devops branch of setup:

{
  "azure-devops": {
    "command": "npx",
    "args": ["-y", "@azure-devops/mcp", "<org>", "--authentication", "azcli"]
  }
}

On --tenant (corrected): omit it by default. Do not auto-fill it from az account show --query tenantId — that returns the user's home/default tenant, which in consultancy/guest setups is frequently not the tenant that owns the ADO org, and hardcoding the wrong one breaks token resolution. AzureCliCredential resolves the tenant from the session; only pass --tenant <id> for a genuine cross-tenant case, and then it must be the ADO org's tenant, sourced deliberately.

Caveat worth documenting (not a code change): azcli reduces prompts but cannot eliminate them where org Conditional Access enforces a sign-in frequency — the user still re-runs az login when the CA window lapses (we hit AADSTS70043 for exactly this). With azcli that's the only time a prompt appears, vs. every run today.

The server is Entra-only for interactive/CLI auth (azcli or interactive); it also has a non-interactive pat mode reading PERSONAL_ACCESS_TOKEN from env (base64 email:pat) for environments that prefer a PAT. See the inline note on setup.md. Verified end-to-end on a downstream install.

server that matches `tracker.type`:
- jira → the Atlassian MCP (`npx -y mcp-remote https://mcp.atlassian.com/v1/mcp/authv2`), exactly
as shipped in `${CLAUDE_PLUGIN_ROOT}/.archon/mcp/ticket-spec-tracker.json`.
- azure-devops → the Azure DevOps MCP server the team uses.

@orioltf orioltf Jun 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When setup writes the Azure DevOps MCP server here, suggest emitting the --authentication azcli flag so it reuses the user's az login session instead of the default interactive OAuth (which prompts a browser login on every workflow run):

"args": ["-y", "@azure-devops/mcp", "<org>", "--authentication", "azcli"]

With azcli the user logs in once (az login) and back-to-back ticket runs reuse the cached token — no per-run prompt (until org Conditional Access forces a fresh az login).

Correction / caveat on --tenant: do not auto-fill --tenant from az account show --query tenantId — that returns the user's home/default tenant, which in consultancy/guest setups is often not the tenant that owns the ADO org (e.g. a vendor dev signed into their own tenant working on a client's org). Hardcoding the wrong tenant breaks token resolution. Omit --tenant by default and let AzureCliCredential resolve it from the session; only add it for a genuine cross-tenant case, and then it must be the ADO org's tenant id, sourced deliberately — not the user's default.

@orioltf orioltf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc suggestion: note the concurrency model (worktree off → serial runs)

With worktree.enabled: false, the workflow runs on the current branch in a single shared working tree + git index, so Archon serialises runs — a second run while one is in progress fails (must use the current branch). This surprised me when trying to groom several tickets at once; worth a short note in the README so users know it's by design and how to parallelise. Suggested text inline on the Notes section.

- This bundle lives in the shared `unic-agents-plugins` repo and ships only the
config **template** (`ticket-spec.config.example.yaml`). Each project copies it to
`ticket-spec.config.yaml` and fills in its own tracker/repo detail; the active
config is never committed to the shared bundle.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a short Concurrency & parallel runs note here. Because worktree.enabled: false, the workflow runs directly on the current branch (one shared working tree + git index), so Archon serialises runs — starting a second run while one is in progress fails (it reports the run must use the current branch). It's worth stating this is by design (Archon isolates per worktree, not per file, so it can't assume two runs won't clash — and any node that writes the persisted .md or commits shares the one git index), plus the escape hatch:

## Concurrency & parallel runs

`worktree.enabled: false` runs on the current branch (one shared working tree +
git index), so Archon serialises runs — a second run while one is in progress
fails. To run tickets in parallel, isolate each run with the CLI `--branch` flag
(creates a dedicated worktree + branch per run):

    archon workflow run unic-ticket-specification --input <id> --branch spec/<id>

Avoid `worktree.enabled: true` globally unless any write/commit step is reworked
to target a stable branch, or commits scatter across throwaway run branches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants