<!-- Authored per the the-loop:writing skill. -->
Tasks: Jira as a first-class work-item source and update channel
Derived from the approved
requirements.md,design.mdandtesting-plan.md. No approval gate: the DAG advances on shape (issue-281).
The work is 28 implementation tasks in five groups, one group per stacked PR (design § Delivery plan), then verification and review. Each group starts with its red tests and ends with its docs. Each PR is opened with the-loop pr create --work-item github:MadaraUchiha-314/the-loop#475, based on the previous PR's branch. Every task is test-first: the named test is written, run red, then made green, and the red→green is noted in the commit that carries it.
Two decisions from the gate are carried here:
- One Jira site per deployment. The design-approval reply did not object to Q4.
- T11 (real Jira sandbox) is conditional. No sandbox was offered at the gate, so task V1 escalates it rather than skipping it silently.
Task list
PR 1 — identity (#476: spec chain + refs, spec ids, origin repository)
- [x] 1.1 Red tests for the Jira ref scheme and spec ids. Add
test_jira_refs.pywith round-trip, grammar rejections (site, key, number), slug and URL,spec_iddisjointness (including keyISSUE),derive_refforjira-<key>-<n>, andorigin_repository(mapped, mirror-only, unknown project, wrong site).- Depends on: none
- Requirements: R1.1–R1.3, R1.5, R2.1, R2.2
- Test: T1, T7 (abuse case 8:
test_ref_on_unknown_site_sends_no_credential) — red
- [x] 1.2
sessions/refs.py:RefScheme,GitHubScheme,JiraScheme,SCHEMES.- Move today's GitHub parse/render/url/slug into
GitHubSchemebyte for byte. WorkItemRef.parse/.ref/.url/.slugdelegate to the scheme, and aspec_idproperty is added.- Replace the 11 literal
provider == "github"checks with scheme lookups wherever they mean "how is this ref shaped". Checks that mean "is this GitHub" stay. - Depends on: 1.1
- Requirements: R1.1–R1.4
- Test: T1 —
test_jira_refs.pygreen;test_routing.py,test_graph_refs.py,test_graphlink.py,test_core_graphs.pygreen unmodified
- Move today's GitHub parse/render/url/slug into
- [x] 1.3 One spec-id derivation.
graphlink.spec_id_forandlifecycle/contract.py:137both callref.spec_id.graph/refs.derive_refinvertsjira-<key>-<n>, taking the site from config.- Registering a ref whose Jira key moved refuses a second spec folder and reports both paths.
- Depends on: 1.2
- Requirements: R2.1–R2.4
- Test: T1 —
test_issue_project_key_never_collides_with_github,test_moved_jira_key_refuses_second_spec_folder
- [x] 1.4
origin_repository(ref, config)and the shared call sites. Swap the six owner/repo reads listed in design C2:dispatcher._repo_payload, the PR endpoint ref, thegraphlinkorigin_repo/_checkout_belongs_to/link_prcalls, and thecore/sessionscontrol-start payload. GitHub-only verbs given a Jira ref raise the named error.- Depends on: 1.2
- Requirements: R1.5, R8 (worktree from the mapped repository)
- Test: T1 —
origin_repositorycases; T9 full suite
- [x] 1.5 Docs for PR 1.
- Add the Jira ref grammar and spec ids to the capability doc that documents work-item refs (
docs/capabilities/control-plane.md, or a newwork-items.mdif the review prefers one), with a history row. - Fix the
jira: is reserveddocstrings. - Record the docs touched in
evidence/documentation.md. - Depends on: 1.3, 1.4
- Requirements: R9.2
- Test: T9 — markdownlint
- Add the Jira ref grammar and spec ids to the capability doc that documents work-item refs (
PR 2 — control-plane integration (client, provider, config, decision)
- [x] 2.1 Decision record. Add
docs/decisions/decision-<nnn>.md: the pycontribsjiraSDK on REST v3/ADF (Cloud) and v2/wiki (DC), one site per deployment, and a project→repository map. It supersedes decision-042 point 13. Add its row indecisions.md.- Depends on: 1.5
- Requirements: R3.8, R9.1
- Test: T9 — markdownlint
- [x] 2.2 Red tests for config, schema and migration 0.12.0. These are
test_jira_config.pyand new cases intest_migrations.py:- the stub migrates, idempotently;
- an old stub is refused with the migration hint;
- the schema rejects a literal token, a bad key,
cloud-scopedwithoutcloudId, andtransport/cli; - a mirror-only project is valid.
- Depends on: 1.5
- Requirements: R3.4–R3.6
- Test: T8 — red
- [x] 2.3 Schema + migration.
- Replace
integrations.jiraincli/the_loop/schemas/cli-config.schema.jsonand its.the-loop/copy. - Add
migrations._migrate_jira_stub, bumpCURRENT_CONFIG_VERSIONto0.12.0, and extendneeds_migration. - Depends on: 2.2
- Requirements: R3.4–R3.6
- Test: T8 — green, including
test_config_schema_parity.py
- Replace
- [x] 2.4 Red tests for
JiraApiConfig/JiraClientagainstFakeJiraClient.- Add
cli/tests/jirafakes.pyandtest_jira_api.py. - Cover the server URL and REST version per deployment, credentials read at call time, missing-credential errors, and error scrubbing.
- Depends on: 2.3
- Requirements: R3.4, R3.5
- Test: T2, T7 (abuse case 9:
test_jira_errors_and_logs_carry_no_secret) — red
- Add
- [x] 2.5
jiraapi.py.JiraApiConfig,JiraClientoverjira.JIRA(lazy, withget_server_info=False), the SDK-logger redaction filter, andJiraApiError. Addjira>=3.10,<4tocli/pyproject.tomldependencies and updateuv.lock.- Depends on: 2.4
- Requirements: R3.4, R3.5, R3.8
- Test: T2 — green
- [x] 2.6
JiraProvider+resolve()branch.- Red first: add
JiraProvider(client=FakeJiraClient())toALL_PROVIDERS, plustest_jira_integration_provider.pyfor per-op shapes, thecreate-labelno-op, andtransitionwith a single or ambiguous candidate. - Then add
graph/integrations/jira.pyand thejirabranch inresolve(), which refusescli. - Depends on: 2.5
- Requirements: R3.1–R3.3, R3.7
- Test: T3 — red → green
- Red first: add
- [x] 2.7 Docs for PR 2.
- Capability doc for integrations (
docs/capabilities/control-plane.md§ integrations). docs/config/reference forintegrations.jira.- A
the-loop doctorJira credential check, with its test. evidence/documentation.mdrows.- Depends on: 2.1, 2.6
- Requirements: R9.2
- Test: T2 (doctor check), T9
- Capability doc for integrations (
PR 3 — Jira as ledger and channel
- [x] 3.1 Red tests for labels and format.
test_jira_labels.pycovers the mapping table and the no-safe-form failure.test_jira_format.pyuses golden files undercli/tests/fixtures/jira/: Markdown→ADF, Markdown→wiki and ADF→Markdown for the checklist, request-review, ask and PR-briefing bodies, with taskList ⇄- [x], tables, code, and<details>dropped.- Depends on: 2.7
- Requirements: R4.4, R4.5, R5.5
- Test: T2 — red
- [x] 3.2
jiralabels.py+jiraformat.py. Addmarkdown-it-pyto dependencies. Label validation is wired into config load.JiraClientconverts bodies by REST version.- Depends on: 3.1
- Requirements: R4.4, R4.5
- Test: T2 — green; T10 bodies generated
- [x] 3.3 Self-marker for Jira.
- Red first:
test_jira_self_comment_never_resumes, with the marker variant and theauthor_id == myselfvariant. - Then add
authz.JIRA_SELF_MARKER, makeis_self_authoredmatch either marker, setJiraComment.is_self, and have the Jira attribution line carry the sentinel. - Depends on: 3.2
- Requirements: R4.6
- Test: T7 (abuse case 4) — red → green
- Red first:
- [x] 3.4 Hooks resolve the integration from the ref.
- Red first:
test_set_phase_label_on_jira_keeps_one_loop_label, plus a selection hook reading a Jira checklist throughlist-comments. - Then add
integration_for(ref, config)and replace the sixresolve("github", …)callers:sideeffects,selection,goal,review,runtimeandchannels/inbound. - Depends on: 3.3
- Requirements: R4.3, R5.5
- Test: T4 — red → green; T9 GitHub hook tests unmodified
- Red first:
- [x] 3.5
RoutedLedger+JiraLedger.- Red first:
test_routed_ledger_records_jira_event_on_jira,test_pr_event_still_recorded_on_githubandtest_jira_ledger_stamps_visible_marker. - Then extract
channels/bodies.pyfromGitHubLedger, addRoutedLedger, and makeload_ledgerhonourchannels.ledger. - The schema enum gains
jira.work-item.create→create_issue. - Depends on: 3.4
- Requirements: R4.1
- Test: T4 — red → green
- Red first:
- [x] 3.6
JiraChannel+ room grammar (mirror-only included).- Red first:
test_mirror_only_project_accepts_room_refuses_work_item,test_jira_channel_without_room_mirrors_nothing,test_comment_on_mirror_ticket_never_reaches_session, andjira@PROJ-1parse/refusal tests. - Then add
_load_jirainCHANNEL_PROVIDERS, ajirarow inworkchannels.CHANNEL_TYPES, and thechannels.jiraschema withenabled,subscribeandverbosity, and nopublish. - Depends on: 3.5
- Requirements: R4.2
- Test: T4 — red → green
- Red first:
- [x] 3.7 Docs for PR 3.
docs/capabilities/channels.md: the Jira ledger, the Jira channel, the mirror-only setup and the label mapping table.docs/config/forchannels.jiraandchannels.ledger.- Commit
evidence/jira-bodies.md(T10). evidence/documentation.mdrows.- Depends on: 3.6
- Requirements: R9.2
- Test: T10, T9
PR 4 — ingress (poller, webhook doorbell, allow-list)
[x] 4.1 Allow-list by provider.
- Red first:
test_jira_authz.py, covering an exactaccountIdmatch, an unlisted author,test_missing_actor_is_unauthorized_on_jira, and DCkey. - Then add
authz.is_authorized_on, the explicitjiraproperty in theauthorizedUsersschema, and poller/dispatcher calls keyed by the ref's provider. - Depends on: 3.7
- Requirements: R7.1–R7.3
- Test: T5, T7 (abuse case 3) — red → green
- Red first:
[x] 4.2
JiraPollProvider.- Red first:
test_jira_poller.py, covering one JQL clause per label,test_jql_values_are_quoted, comments,test_done_status_category_is_closure,test_rate_limited_scope_keeps_cursorand owns/presence. - Then add
poller/jira.pyregistered underjira.from_sourcegainsconfig._build_providerspasses it. Thepolling.sources[].providerenum gainsjira, and the schema requires projects that have arepository. - Depends on: 4.1
- Requirements: R5.1–R5.6
- Test: T5, T7 (abuse case 6) — red → green
- Red first:
[x] 4.3 Webhook doorbell.
- Red first:
test_jira_webhook.py, coveringtest_jira_webhook_rejects_bad_signature,test_jira_webhook_absent_without_secret,test_doorbell_refetches_comment_and_issueand the event mapping. - Then add
webhook/jira.py, the conditional/jira-webhookroute inserver.py, and the secret env inwebhook/daemon.py. - Depends on: 4.2
- Requirements: R6.1–R6.3
- Test: T5, T7 (abuse cases 1, 2) — red → green
- Red first:
[x] 4.4 Integration scenarios for ingress. Add
test_jira_integration.py, with Gherkin docstrings andRequirement:links:- an authorized Jira comment resumes the work item's session
- the same Jira comment by webhook and by poll is delivered once
- a Jira phase-selection checklist ticked in place is read at execute
Also add
test_jira_label_alone_does_not_startandtest_jira_comment_is_framed_untrusted.- Depends on: 4.3
- Requirements: R5.3, R5.5, R6.3, R6.4
- Test: T6, T7 (abuse cases 5, 10)
[x] 4.5 Docs for PR 4. Polling and the Jira webhook in
docs/capabilities/webhook-triggers.md,docs/config/forpolling.sources[]Jira androuting.authorizedUsers[].jira, Jira webhook setup steps (admin-registered, secret), andevidence/documentation.mdrows.- Depends on: 4.4
- Requirements: R9.2
- Test: T9
PR 5 — edges (linkage, verbs, onboarding, skill text)
- [x] 5.1 PR → Jira linkage.
- Red first:
test_pr_naming_unregistered_jira_key_does_not_linkandtest_pr_in_other_repository_does_not_link, plus the positive scenario a PR naming a registered Jira key routes to the Jira work item. - Then add
SOURCE_JIRA_KEYinwebhook/router.linked_work_item_sources, matching the branch and title only, with registration and repository checks. - Depends on: 4.5
- Requirements: R8.1
- Test: T6, T7 (abuse case 7) — red → green
- Red first:
- [x] 5.2 Ticket verbs dispatch by tracker.
- Red first: tests for
ticket show/create --project/close,commentandaskon a Jira ref, plus the scenarios finish-tasks transitions the Jira ticket to Done and the-loop comment on a Jira ref is recorded on the Jira ticket. - Then add
core/tickets.tracker_for(GitHub functions moved unchanged) andJiraTickets.ticket creategains a--projectflag, mutually exclusive with--repository.pr createon a Jira ref usesorigin_repository. - If a REST route shape changes, update
docs/api-specs/openapi, and T12 becomes applicable. - Depends on: 5.1
- Requirements: R8.2, R3.7
- Test: T6 — red → green; T9
- Red first: tests for
- [x] 5.3 Skill, commands and
/init.commands/work-on.md,create-ticket.md,finish-tasks.mdandskills/the-loop/reference/automation.mdreplace the PR-ref workaround with thejira:flow, keeping MCP as the no-CLI fallback.commands/init.mdandreference/onboarding.mdgain the Jira onboarding group. Credentials are named by environment variable only.- Depends on: 5.2
- Requirements: R8.3, R8.4, R9.3
- Test: reviewed at self/critic review; T9 markdownlint
- [x] 5.4 Docs for PR 5.
docs/capabilities/cli.mdfor the verbs.- README/docs-site lines that say Jira works "via MCP only".
evidence/documentation.mdcomplete.- Depends on: 5.3
- Requirements: R9.2
- Test: T9
Verification and review (top of the stack)
These two are the graph's own verification and review nodes, not implementation tasks, so they carry no checkbox: the nodes gate them (testing-plan.md's activities and the evidence/ records).
V1 Verify. Run
testing-plan.mdT1–T10 on the top of the stack. Then T11:- If a sandbox is configured, run the manual procedure and commit the redacted evidence.
- Otherwise, leave T11 unticked, record why, and escalate on the PR with
the-loop ask.
Fill Verification results and write
evidence/verification.md.- Depends on: 5.4
- Requirements: all
- Test: T1–T11
V2 Review chain. This step happens in the graph's review nodes, not as an implementation task:
- self and critic rounds per
the-loop critic policy; - the security review, where tier 4 requires a named human sign-off;
- capability-docs record;
- the reviewer briefing on each PR.
- Depends on: V1
- Requirements: all
- Test: T7 re-run,
evidence/security-review.md
- self and critic rounds per
Dependency graph (DAG)
The chain is almost linear by design. Each PR is based on the one before it, so reviewers read the stack in order. Inside a PR, the only parallel branches are 1.3/1.4 and 2.1/2.2.
Checkpoints
- After every task:
- run
cd cli && uv run python -m pytest -qon the touched test files; - tick the box;
- commit with the red→green noted in the message;
- then compact the context (
reference/context.md).
- run
- At the end of each PR group:
- run
uv run pre-commit run --all-files(T9); - push;
- open the PR with
the-loop pr create --work-item …, based on the previous branch; - record it in
evidence/pull-requests.md; - republish the spec artifact if a spec file changed.
- run
- After 5.4: the
verificationnode runs V1, then the review nodes run V2.
Review comments
Appended by the-loop's
record-feedbackhook when a human gate approves with comments (issue-109). Append-only and attributed: an approval never silently discards a reviewer's suggestions, and the feedback travels with the document it concerns rather than living in a side-channel tracker.