Usage#

Day-to-day workflow for the shared snell- corpus: scaffold and edit responses, quality-gate in CI, browse with the TUI, convert with pandoc, and use free-tier Jira REST only for explicit probe / import / one-shot comment — never unattended bulk sync into Jira.

Quick start#

cargo install --git https://github.com/HaoZeke/canonic --locked
canonic doctor
canonic list
canonic tui                          # interactive browser
canonic check && canonic lint --engine harper

From a checkout of this repo (corpus sample included):

cargo build --release
./target/release/canonic list
./target/release/canonic tui

Operational loop#

  1. 1 Scaffold or import new for a template, or import-jira into corpus/imports/.
  2. 2 Edit & gate check + Harper lint; promote only when clean.
  3. 3 Browse & search TUI or CLI list/search/dedupe before publishing.
  4. 4 Render / post convert for wiki paste, or explicit jira-comment (one file → one issue).

Command reference#

Command

Purpose

Notes

doctor

Probe pandoc, Vale, Harper, optional Jira env

Exit 1 if pandoc missing

tui

Interactive corpus browser

See Interactive TUI

list

List published responses

--corpus DIR

new "Title"

Scaffold check-clean snell- markdown

--id, --tags, --sop, --out, --force

promote PATH

Copy import draft → corpus/responses/

Runs quality check first; refuses dirty drafts

check

Quality gate (prefix, sop, team sign-off)

Exit 1 on findings; --json

lint

Vale and/or Harper

--engine harper for CI (in-process)

convert [PATH]

Markdown → Jira wiki via pandoc

Whole corpus if PATH omitted; --write

reindex

Rebuild Tantivy index

Default .canonic-index/

search QUERY

BM25 full-text search

Requires prior reindex

dedupe

Near-duplicate pairs

--threshold, --reindex, --json

jira-probe

Free REST identity (/myself)

Env auth; no Marketplace apps

import-jira JQL

Pull comments as review drafts

Writes corpus/imports/ only

jira-comment

Post one pandoc-jira comment

Explicit, not bulk; --dry-run

Command map (cards)#

Environment & corpus
  • canonic doctorpandoc / tooling status
  • canonic tuiinteractive browser
  • canonic listlist response files
  • canonic checkquality gate · exit 1 on findings
  • canonic lint --engine harper
Author & promote
  • canonic new "Title"scaffold snell- template
  • canonic promote PATH.mdimport → responses after check
Search & dedupe
  • canonic reindex
  • canonic search "project space backup"
  • canonic dedupe --reindex --threshold 1.0
  • canonic dedupe --threshold 0.5 --json
Convert & free Jira
  • canonic convert PATH.mdpandoc jira writer
  • canonic jira-probemyself · free platform only
  • canonic import-jira "JQL"drafts under corpus/imports/
  • canonic jira-comment --issue KEY PATH.mdexplicit POST comment

Interactive TUI#

canonic tui opens a ratatui corpus browser over corpus/responses/ (override with --corpus).

Key

Action

j/↓

Next response

k/↑

Previous response

/

Filter by id, title, tags, or body

C

Run quality check on the whole corpus

c

Convert selection → jira wiki (preview only)

l

Lint selection with in-process Harper

r

Rebuild Tantivy index

s

Search index (filter text or selected title)

R

Reload markdown from disk

d

Doctor / tooling snapshot in preview

?

Help overlay

q

Quit

Important

The TUI never POSTs to Jira. Convert preview stays local. Publishing is always an explicit canonic jira-comment (or human paste of convert output).

Recipes#

New response from scratch

The filename is snell- plus a slug of the title (override with --id).

canonic new "Project space is not a backup" --tags storage,project-space
# → corpus/responses/snell-project-space-is-not-a-backup.md
$EDITOR corpus/responses/snell-project-space-is-not-a-backup.md
canonic check
canonic lint --engine harper
canonic tui

Import → edit → promote

export JIRA_BASE_URL=https://your-instance.atlassian.net
export JIRA_EMAIL=you@example.org JIRA_API_TOKEN=...
canonic jira-probe
canonic import-jira "project = HSP AND labels = canned-response" --dry-run
canonic import-jira "project = HSP AND labels = canned-response"
$EDITOR corpus/imports/snell-….md    # team sign-off, shared voice
canonic promote corpus/imports/snell-….md
canonic check

Search before answering a ticket

canonic reindex
canonic search "project space backup"
canonic dedupe --reindex --threshold 1.0
canonic tui    # / to filter, c to preview jira markup

Publish one comment (human-gated)

canonic convert corpus/responses/snell-demo-shared-quota.md
canonic jira-comment --issue HSP-101 \
  corpus/responses/snell-demo-shared-quota.md --dry-run
canonic jira-comment --issue HSP-101 \
  corpus/responses/snell-demo-shared-quota.md

Full paste-ready session#

canonic doctor
canonic list
# shipped demo (no scaffold required):
canonic convert corpus/responses/snell-demo-shared-quota.md
canonic check
canonic lint --engine harper
canonic tui

# scaffold a new response (slug from title):
canonic new "Example topic" --tags example
# → corpus/responses/snell-example-topic.md
$EDITOR corpus/responses/snell-example-topic.md
canonic check
canonic convert corpus/responses/snell-example-topic.md

canonic reindex
canonic search "shared quota"
canonic dedupe --reindex --threshold 1.0

JIRA_BASE_URL=https://your-instance.atlassian.net \
JIRA_EMAIL=you@example.org JIRA_API_TOKEN=... \
  canonic jira-probe
canonic import-jira "project = HSP AND labels = canned-response" --dry-run
canonic promote corpus/imports/snell-example-hsp-101.md
canonic jira-comment --issue HSP-101 \
  corpus/responses/snell-demo-shared-quota.md --dry-run

Corpus layout#

Responses live under corpus/responses/ as snell-<topic-slug>.md. Import drafts land under corpus/imports/ (gitignored) until promote. Front matter is enforced by canonic check:

---
id: snell-<topic-slug>
title: Human-readable title
prefix: snell
tags: [tag-one, tag-two]
sop: none
---

Important

  • id and filename stem must match and start with snell-

  • prefix: snell required (shared library convention; no personal prefixes)

  • sop: required — Confluence / service-desk wiki URL, or the literal none when no SOP page exists yet

  • Closings must be team-generic (e.g. Support Team), not personal names

.gitignore excludes the Tantivy index under .canonic-index/ and review drafts under corpus/imports/ (never commit un-promoted imports).

Team review via GitLab mirror#

For team merge-request review on a GitLab remote while the primary clone is elsewhere, mirror a branch:

export CANONIC_GITLAB_REMOTE=git@gitlab.example.com:your-group/canonic.git
scripts/mirror-to-gitlab.sh

Open the merge request on that GitLab. Do not bulk-push the library into Jira; only convert paste or explicit jira-comment publish one answer.

CI quality gate#

GitHub Actions runs cargo test --locked --all-targets, then on the release binary:

  1. canonic list / canonic check on corpus/responses/

  2. canonic lint --engine harper (in-process; domain vocab for tooling jargon)

  3. canonic convert smoke on the seeded sample (pandoc installed in CI)

Keep published responses check-clean so the gate stays green.

Dedupe#

dedupe rebuilds or reuses the Tantivy index, then for each response runs a self-query (title + content terms) and reports other documents that rank above --threshold. Pair reasons include the Tantivy score and a content Jaccard similarity for a second opinion.

Tip

Use a high threshold when you only want strong near-copies (curation before a Jira migration). Drop the threshold and add --json when you want a wider review list.

Free Jira REST (no paid Marketplace apps)#

canonic uses only native platform REST (Cloud Free API tokens or Server/DC PAT). No Marketplace extensions, ScriptRunner, or paid service-desk canned-response admin APIs.

Probe

canonic jira-probe calls GET /rest/api/2/myself (and serverInfo when available). Exit non-zero on auth or reachability failure.

Import (read path)

import-jira <jql> searches free search paths (api/2 then api/3 variants), fetches comments, converts wiki/ADF → markdown, and writes one draft per issue under corpus/imports/ — never into corpus/responses/. --dry-run lists targets without fetching comments.

Comment write (explicit)

jira-comment --issue KEY PATH.md converts markdown with pandoc’s free jira writer, then posts via platform REST:

  • Server/DC: POST /rest/api/2/issue/{key}/comment with wiki string body

  • Cloud Free (*.atlassian.net): POST /rest/api/3/issue/{key}/comment with minimal ADF — required by Cloud v3, no paid apps

--body-format auto|wiki|adf overrides host detection. --dry-run prints the converted wiki without POSTing. One file, one issue, human-gated — not bulk sync.

Official free map (platform only)

Command

Endpoint

Body

jira-probe

GET /rest/api/2/myself

import-jira

GET …/search + comments (v2/v3 fallback)

wiki or ADF read

jira-comment

POST …/comment (v2 wiki / v3 ADF)

pandoc jira text

Authentication (environment):

Variable

Role

JIRA_BASE_URL

Required, e.g. Cloud Free instance URL

JIRA_EMAIL + JIRA_API_TOKEN

Basic auth (Jira Cloud Free API token)

JIRA_AUTH_HEADER

Raw Authorization header; wins if set (e.g. Bearer for Server/DC)

Agent skill#

Day-to-day agent instructions ship at .agents/skills/canonic-canned-loop/SKILL.md (import → scaffold/promote → check/lint → TUI → convert / jira-comment). Install or trust that path in your agent skill loader.

Exit codes#

Command

Non-zero meaning

doctor

1 if pandoc missing (convert blocked)

check

1 if any quality finding

lint

1 if any lint finding

promote

check failed or destination exists without force

GitLab mirror#

scripts/mirror-to-gitlab.sh pushes the current branch to a second remote for team merge-request review on self-hosted GitLab:

CANONIC_GITLAB_REMOTE=git@gitlab.example:group/canonic.git scripts/mirror-to-gitlab.sh

Pass a branch name as the first argument to override the current branch.

Jira smoke (Docker / Nix)#

CI and local free-tier smoke (fixture container, no Marketplace apps):

cargo build --release
./scripts/ci/jira-docker-smoke.sh

Nix dockerTools image:

nix build .#jira-fixture-image
docker load < result
./scripts/ci/jira-docker-smoke.sh

Heavy official Atlassian Jira Software (developer timebomb; multi-GB RAM):

./scripts/jira-real/run-import-smoke.sh