symphony-workflow
CLAUDE.md0 starsChanged 33 days agoMITView on GitHub
Guidance for Claude Code (claude.ai/code) working in this repository. Auto-loaded every session. Keep concise — essential rules only. Ticket status → the ADLC Jira board | Last state → docs/context/session-summary.md (pattern borrowed from example-nextjs-app's own proven session-handover convention) symphony-workflow is now two things, not one: a Python CLI for the Jira Cloud REST API where every operation is also a schema-described tool call, and a Symphony-inspired orchestration service (FastAPI server + background poller) that drives a ticket through a Plan/Implement…
- Reads credentials
- Installs packages
In this repository2 other files it gives its agents.
- shippingSkill
- symphony-workflow-opsSkill
The file
We split this CLAUDE.md into the rules it gives an agent
8 rules. About 0 are common, 2 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 88
Never claude -p. See the "Why Claude Agent SDK" section of docs/plans/0001-symphony-poc/spec.md.
- Very specific: line 130
Every workspace's branch is {ISSUE-KEY}-{slug-of-summary} (workspace.py's branch_name()), e.g.
- Very specific: line 157
Unit tests are required, not optional: No PR-worthy change ships without tests.
- Very specific: line 259
Keep the three test files scoped to their layer: test_client.py owns HTTP correctness, test_tools.py owns schema validation + dispatch, …
- Very specific: line 270
Follow ruff defaults (pyproject.toml → [tool.ruff], line length 100).
- Very specific: line 315
Don't hardcode cloud-f1.atlassian.net, CF1's custom field IDs, ADLC, or a specific target repo path into …
- Distinctive: line 319
Don't reach for the v3 API / ADF format without a documented reason — it changes every model and client method to be structured instead of …
- Distinctive: line 321
Don't assume a correctly-named branch alone gets Jira auto-linking — see the Branch naming section above for the current status of the …
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
Guidance for Claude Code (claude.ai/code) working in this repository.
Auto-loaded every session. Keep concise — essential rules only. Ticket status → the
ADLCJira board | Last state → docs/context/session-summary.md (pattern borrowed fromexample-nextjs-app's own proven session-handover convention)
Project Overview
symphony-workflow is now two things, not one: a Python CLI for the Jira Cloud REST API where
every operation is also a schema-described tool call, and a Symphony-inspired orchestration
service (FastAPI server + background poller) that drives a ticket through a Plan/Implement
loop via the Claude Agent SDK. Both are first-class — the CLI didn't stay "just a CLI." The
Plan/Implement run-loop also works against Linear, selected via
SYMPHONY_WORKFLOW_TRACKER=jira|linear (ADLC-57, tracker.py's TrackerClient Protocol) — the
tool-call layer (tools.py) and decompose/sync-decomposition stay Jira-only; see
docs/plans/0005-linear-tracker-backend/intent.md
for why. See README.md for the CLI pitch and confirmed CF1 field IDs; see
docs/plans/0001-symphony-poc/intent.md /
docs/plans/0001-symphony-poc/spec.md /
docs/plans/0001-symphony-poc/implement.md
for the orchestration service's why/design/roadmap (read those three before touching api.py,
orchestrator.py, poller.py, runs.py, or agent_runner.py) — the general convention behind
this docs/plans/<NNNN>-<slug>/ layout is docs/plans/README.md (ADLC-43); see
docs/PORTABILITY.md for running this against a different repo/Jira
project. New to this repo? Load the symphony-workflow-ops skill first
(.claude/skills/symphony-workflow-ops/SKILL.md) — the short operational reference this file's
sibling docs don't repeat. Setting this up for the first time as a human (not a Claude Code
session)? docs/QUICKSTART.md is a linear walkthrough, not reference material.
This is a standalone package: a public (MIT) repo in the cloud-f1 GitHub org, usually cloned next to
your other repos. It is not part of any other repo of the org — don't assume its rules apply here beyond
what this file states. Because it is public, keep client and personal names, local paths and
credentials out of code, comments, docs and docs/runs/ (git history keeps what was committed before).
Jira tracking for this repo's own work
Work on this repo is tracked in Jira project ADLC (cloud-f1.atlassian.net, Kanban board
74), not CF1 — CF1 is content/marketing work, ADLC is agent-driven dev work. Structure:
Epic → Story (one per meaningful chunk of the epic's work) → Subtask per concrete
work item. When you pick up a subtask, transition it as you go
(symphony-workflow transition <key> "In Progress" / "Done") — this repo's own agent-tool CLI
is the thing doing the tracking, so use it. See
docs/plans/0001-symphony-poc/intent.md's Tracking
table for ADLC-2's original phase → ticket mapping.
Hard rule (Alex, 2026-09-26): never build directly under an Epic ticket. Every Epic gets
decomposed into Stories (and Subtasks as needed) before or while the work happens —
symphony-workflow decompose <epic-key> (read-only, writes docs/runs/<epic>/decomposition.md
for review) then symphony-workflow sync-decomposition <epic-key> (materializes the reviewed
decomposition as real Story/Subtask issues, parent=<epic-key>) exist exactly for this — see
ADLC-46/47. Work happens against the Stories/Subtasks, each transitioned as it completes; the
Epic itself only transitions to Done once its children are. Real incident this rule fixes:
ADLC-57 (issue type Epic) was built end-to-end and shipped with zero Story/Subtask children —
caught only after the fact, then retroactively decomposed once this was noticed (see
docs/plans/0005-linear-tracker-backend/implement.md's closing entry). ADLC-53/ADLC-58 were
Story/Task, not Epics, so this specific rule didn't apply to them, but the same instinct — do the
work directly under one ticket instead of breaking it down first — is what to watch for. Don't
repeat this — decompose first.
Stories without Subtasks are the same mistake one level down (one consumer: 33 of 73; another:
all 26). Run symphony-workflow audit <PROJECT> --open before and after a batch of work; fix with
symphony-workflow add-subtasks <plan.json>. The rules (naming, size, done-when, roll-up, one owner) are
in docs/JIRA_HIERARCHY.md.
Claude Agent SDK usage (agent_runner.py)
Confirmed 2026-09-24 against code.claude.com/docs/en/agent-sdk — pip install
claude-agent-sdk (installed, v0.2.159+). Entry point: async def query(*, prompt,
options: ClaudeAgentOptions) -> AsyncIterator[Message]. Three settled decisions, don't
re-litigate without updating docs/plans/0001-symphony-poc/spec.md first:
- Plan phase uses
permission_mode="plan"(a built-in SDK mode — explore without editing), not a hand-rolled read-only tool allowlist. This is simpler and safer than what was originally speculated. - Implement resumes the Plan phase's session via
options.resume=<session_id>rather than starting fresh — the agent keeps the context of the plan it already wrote. - Plan phase also sets
ClaudeAgentOptions.output_format={"type": "json_schema", "schema": PLAN_OUTPUT_SCHEMA}(ADLC-28) — structured output lands onResultMessage.structured_outputand is captured asAgentRunResult.structured/Run.plan_structured, alongside (not instead of) the existing free-textresult.text/Run.plan. Confirmed by a throwaway spike that this survives plan-mode's exploratory multi-turn tool-use loop cleanly — seedocs/plans/0001-symphony-poc/spec.md. Implement phase stays free-text (nooutput_format); this is the minimal foundationADLC-46's Epic/Story/Task decomposition schema builds on, not that feature itself.ADLC-29's triage step reuses this same seam rather than introducing a fourth settled decision. - Never
claude -p. See the "Why Claude Agent SDK" section ofdocs/plans/0001-symphony-poc/spec.md.
CLI control plane (plan/implement/decompose/sync-decomposition)
cli.py can also drive a ticket without the FastAPI server, calling the same shared logic
in-process (a fourth way to drive a ticket, alongside the API, the poller, and call/
list-tools — see the symphony-workflow-ops skill):
symphony-workflow plan/implement <key>(ADLC-45) — callsorchestrator.execute_plan/execute_implement, the same functionsapi.py/poller.pyuse.RunStoreis per-process here, soplanpersists itsRuntodocs/runs/<key>/run.jsonfor a laterimplementinvocation to reload.symphony-workflow decompose <key>(ADLC-46) — breaks a Jira Epic into Stories/Tasks for human review. Reuses the Plan phase'spermission_mode="plan"+output_formatwiring (prompts.PLAN_DECOMPOSE_SCHEMAin place ofPLAN_OUTPUT_SCHEMA) — read-only, no Jira writes. Rendersdocs/runs/<key>/decomposition.{md,json}.symphony-workflow sync-decomposition <key>(ADLC-47) — materializes an already-revieweddecomposition.jsonas real Jira Story (parent=<epic-key>)/Subtask (parent=<story-key>) issues via the existingcreate_issuepath. Does not re-run the decompose agent. Refuses an Epic that already has children.symphony-workflow diff-decomposition <key>/sync-decomposition <key> --update(ADLC-146) — read-only content-drift report (exit 0/1/2/3) and its idempotent apply step; cards are tied to spec entries byid+ asd-id:description marker (drift.py). Never deletes. Seedocs/JIRA_HIERARCHY.md.symphony-workflow profile/import-plan/sync-plan(plan 0008, ADLC-197) — local, tracker-free:profilereads how a project works and recommends a setup (two recommendations;--writesavesdocs/context/plan-sync.jsonafter confirming),import-planreads epics-as-markdown files into the neutral plan,sync-plan --to markdownwrites it back as Markdown (idempotent, only managed blocks, never deletes). With no configsync-planprints the recommendation and exits 1 without touching anything.--to linearand--to jirapush it to Linear (project, milestones, issues, sub-issues) or Jira (Epic, Story, Subtask), andsync-status --to linear|jirasyncs To-Do status both ways, conflicts reported not resolved (both sinks verified live on a throwaway project,jira_sink.py/linear_sink.py).split-todos [--approve](ADLC-201) proposes To-Dos for Tasks with none behind a review file;sync-planuses only an approved one, else the five steps.profile_project/import_planare also tools andcallruns them without Jira credentials.create/update/file-cards <manifest.json> [--apply] [--write-keys](plan 0010, ADLC-410) — idempotent card filing, Jira-only.create --identity-label Lsearches the project for labelLfirst (0 hits create + read back by key, 1 return it withexisting, 2+ error);update --add-labeladds without replacing (update.labelsadd, never afields.labelswrite).file-cardsties each manifest item to its card by afu-<id>label, dry run by default, validates the whole manifest first (exit 2, no call), reads every card back (exit 1 on a mismatch), moves status forward only, never reopens a Done card. Tools:create_issue(identity_label),update_issue(labels_add),file_cards. JQL lags a write by seconds, so read-back is a GET by key and--write-keyslets later runs adopt by key. Code:file_cards.py.start <KEY>/pr <KEY>/land <KEY>/check-commit/changelog-draft X.Y.Z/release X.Y.Z/release-backfill(plan 0009, ADLC-336) — the git half of a ticket. Jira links a branch, its commits and its PR to a card from the key in the branch name, sostartmakes{KEY}-{slug}and a worktree from a freshorigin/main(card In Progress);prputs the key in the PR title and body (In Review);landreads GitHub (state and merge commit), checks the merge is onorigin/main, fast-forwardsmain, deletes the remote branch, the worktree and the local branch (never forced; a dirty worktree is kept) and moves the card Done with a roll-up;releaserunsscripts/release.sh, pushes the tag and creates the GitHub Release from the CHANGELOG. Commit subjects aretype(scope): subject (KEY); the plugin'scommit-key-check.pyhook enforces it only on a branch that starts with a ticket key. Optional Jira settings:docs/JIRA_GIT_SETUP.md. Code:ship.py,releasing.py. Tools vs commands (ADLC-393):check_commit_subjectandchangelog_draftare read-onlycalltools (local, no Jira credentials, path scoped, git throughsafepath.run_gitviaship.hardened_run);start,pr,land,releaseandrelease-backfillstay CLI-only on purpose: they change git, GitHub and the tracker,callhas no confirmation or dry run and can be driven unattended by any agent, so Claude Code's per-command permission prompt stays the safety layer. A card already in the target status (a Jira automation may have closed it on merge) is left alone andlandstill rolls up.
See docs/plans/0001-symphony-poc/spec.md's "CLI control plane" section for design detail.
Branch naming — required, not optional
Every workspace's branch is {ISSUE-KEY}-{slug-of-summary} (workspace.py's branch_name()),
e.g. ADLC-24-poller-live-verify-add-a-version-constant. Must start with the issue key —
that's what Jira's Development-panel auto-linking depends on (confirmed missing on ADLC-17
before this was fixed, when the branch was symphony-workflow/ADLC-17). A human doing manual dev
work on a ticket outside the Plan/Implement path should use the same pattern by hand:
git checkout -b ADLC-24-poller-live-verify-add-a-version-constant.
Branch naming alone was not sufficient — a connected app was also required, and now is.
Confirmed 2026-09-24: the GitHub-for-Jira app is installed and connected (ADLC-30, Done) —
GET /rest/dev-status/1.0/issue/summary now shows repository.byInstanceType populated
(previously {} for every data type, confirmed missing as of 2026-09-25 before this was fixed).
Verified end to end on 2026-10-14 (ADLC-336): a ticket started with start and a draft PR opened
with pr showed pullrequest, branch and repository each at 1 on the card's dev-status summary,
about 30 seconds after the push (the app syncs asynchronously: 0 right after the push is normal, wait
a minute before concluding it did not link). Deleting the remote branch afterwards drops the card's
branch entry but keeps the PR.
The three non-negotiables
- Python codebase, CLI type. The public interface is the
symphony-workflowconsole script (Typer), not a library someone imports into unrelated code — thoughJiraClientis a normal typed class and importable when that's genuinely what's needed. Any new capability gets a subcommand or acall-able tool, not a side script. - Jira API is integrated as tool calls, not just an HTTP client. Every Jira operation must
be reachable two ways:
client.py(typed Python method) AND a registeredToolintools.pywith a pydantic input model, exposed viasymphony-workflow list-tools/call. Adding a client method without a matching tool entry is an incomplete change. - Unit tests are required, not optional. No PR-worthy change ships without tests. HTTP
calls are mocked with
respx— nothing in this repo talks to a live Jira instance. If you add a client method, add arespx-mocked test for it in the same change.
Jira API gotchas — confirmed against live cloud-f1.atlassian.net, don't relearn these
epicvsparentoncreate_issueare two different linking mechanisms, not interchangeable.ADLCis a team-managed project ("simplified": true) — Story→Epic and Subtask→Story linking both use the standardparentfield ({"key": "ADLC-27"}).CF1is classic/company-managed and uses the "Epic Link" custom field instead (epic_link_field, e.g.customfield_10014— confirmed correct forCF1only). Passingepic="ADLC-27"againstADLCfails with"cannot be set. It is not on the appropriate screen"; useparent="ADLC-27"there instead.client.py'screate_issuedocstring has the full explanation.- Jira's
add_commentbody renders through wiki markup, not GitHub Markdown. A leading#/##/###there means nested numbered-list depth (1 / a / i), not a heading — GFM ATX headers collide and render as a garbled outline (confirmed live onADLC-28/ADLC-30's plan comments before the fix).orchestrator.py'sjira_safe_comment_body()converts them to bold lines before posting; route any new comment-posting code through it too, don't post agent/plan text to a comment verbatim. /rest/api/2/searchis retired; use/rest/api/3/search/jql(POST, JSON body, not GET+query params).client.py'ssearch_issuesalready does this correctly — but anything posting JQL queries outside the Python client (e.g. a shell hook) has to get this right independently; re-discovered the hard way writing.claude/hooks/session-start-check-api.sh.- Project creation (
POST /rest/api/3/project) works fine via API — it does NOT require the Jira UI (nowsymphony-workflow create-project, ADLC-404) — confirmed live creatingEXMP(ADLC-40), an account with globalADMINISTERpermission (check withGET /rest/api/3/mypermissions?permissions=ADMINISTER) can do this. Two traps: (1)projectTemplateKeynaming is misleading —com.pyxis.greenhopper.jira:gh-simplified-kanban-classiccreates a classic (company-managed) project despite "simplified" in the name (the word "classic" in the key is the real signal); the correct team-managed (next-gen,"simplified": true, matchingADLC's own style) Kanban template iscom.pyxis.greenhopper.jira:gh-simplified-agility-kanban— verify by checking the response'sstyle/simplifiedfields, don't trust the template key name alone. (2)DELETE /rest/api/3/project/{key}soft-deletes into Jira's Trash with a ~60-day retention — the key and name stay reserved the whole time (GET /rest/api/3/project/search?status=deletedlists them), so recovering from a wrong-template creation needs a different key/name for the retry, not just a delete-and-recreate with the same ones.
Architecture
src/symphony_workflow/
config.py # JiraConfig.from_env() — required: JIRA_BASE_URL / JIRA_EMAIL / JIRA_API_TOKEN
# optional: JIRA_FIELD_EPIC_LINK / JIRA_FIELD_SPRINT / JIRA_FIELD_STORY_POINTS
models.py # pydantic response models (Issue, SearchResult, IssueCreateResult, Transition)
client.py # JiraClient — typed HTTP wrapper over Jira REST API v2, raises JiraError
tools.py # Tool dataclass + TOOLS registry + call_tool() — the agent-facing surface
cli.py # Typer app — human subcommands (get/search/...) + list-tools/call
workspace.py # WorkspaceManager — per-issue git worktree, §9.5 safety invariants, branch_name()
agent_runner.py # wraps claude_agent_sdk.query() — the Plan/Implement session runner
prompts.py # Plan/Implement prompt templates (docs/WORKFLOW.md is the policy summary)
runs.py # Run/RunState/RunStore — the Plan/Implement human-gate state machine
orchestrator.py # execute_plan/execute_implement — shared by api.py AND poller.py
rollup.py # Subtask -> Story -> Epic roll-up: all children Done -> parent Done (forward-only)
jira_project.py # create-project / create_project tool: key check, payload, read-back warnings
file_cards.py # file-cards: manifest validation, fu-<id> identity label, read-back, forward-only status (plan 0010)
decompose.py # Epic -> Stories/Tasks, read-only, reuses run_plan()'s output_format wiring
sync_decomposition.py # decomposition.json -> real Jira Story/Subtask issues
drift.py # diff-decomposition + sync --update: ids, sd-id markers, content drift vs Jira
envsetup.py # init-env / check-env: per-project .env creation + key report, never prints values
plan.py # neutral Category -> Task -> To-Do plan, ids, pyramid guardrails (plan 0008)
epics_md.py # source adapter: an epics-as-markdown project's files -> the neutral plan
project_profile.py # `profile`: read how a project works, recommend a plan-sync setup (names only, no secrets)
markdown_sink.py # neutral plan -> Athena-compatible .md files (managed blocks only), checkbox state back
status_sync.py # sync-status: three-way status compare (last sync / plan / Linear), pull + push, conflicts reported
jira_sink.py # neutral plan -> Jira Epic/Story/Subtask, sd-id + id->key map, JiraStatusBackend for sync-status
linear_sink.py # neutral plan -> Linear project/milestones/issues/sub-issues, sd-id idempotent, never deletes
todo_source.py # layer-3 To-Dos: approved agent proposal (split-todos), five workflow steps, 2-to-6 guardrail
ship.py # start / pr / land: branch {KEY}-{slug} + worktree from origin/main, verified-merge cleanup, commit subject check
releasing.py # changelog-draft, release (gate + tag + push + GitHub Release), release-backfill
safepath.py # run_git (repo-config command execution off), is_safe (no symlinks out of the project), scoped_path
poller.py # background asyncio task — auto-discovers + auto-Plans, never auto-Implements.
# Optional triage cadence (ADLC-29, off by default): a slower, batched agent
# turn classifies new tickets plan/skip/flag before the normal dispatch path
api.py # FastAPI app — HTTP surface over tools.py + orchestrator.py
tests/ # pytest + respx, one file per module aboveTwo entry points, one shared core: cli.py and api.py both go through tools.py →
client.py → Jira REST API v2; api.py's /plan//implement and poller.py both go through
orchestrator.py so they can't drift on what "running a plan" means. Never call httpx
directly from cli.py, tools.py, or api.py — go through JiraClient.
Why API v2, not v3: v2 takes plain-text description/comment bodies; v3 requires the
Atlassian Document Format (nested content/paragraph JSON). This org's other Jira tooling
(an internal jira-cloud-f1 skill) already uses v2 successfully against
cloud-f1.atlassian.net — match that proven pattern rather than reintroducing ADF here.
Adding a new tool (the standard change shape)
- Model — add a pydantic args model to
tools.pyif the operation needs new input shape. - Client — add the method to
client.py, calling the right REST endpoint, raisingJiraErrorvia_raise_for_statuson failure. Add arespx-mocked test intest_client.pythat asserts both the request sent (method/path/body) and the response parsing. - Tool — write a
_handlerfunction and register aTool(name, description, args_model, handler)entry inTOOLS. Add a dispatch test intest_tools.pyusing aMagicMockclient (don't re-test HTTP behavior here — that'stest_client.py's job). - CLI (only if a human needs a friendly subcommand — not every tool needs one) — add a
Typer command in
cli.py, add a test intest_cli.py.
Keep the three test files scoped to their layer: test_client.py owns HTTP correctness,
test_tools.py owns schema validation + dispatch, test_cli.py owns argument parsing and exit
codes.
Code style
uvfor all package management — neverpip.uv sync --extra devinstalls everything.- Python 3.11+. Type hints everywhere; pydantic models for anything crossing a boundary (HTTP response, tool input).
- Explicit exception handling — never a bare
except Exception: pass.JiraErrorcarriesstatus_code; catch it specifically, notException. - Follow
ruffdefaults (pyproject.toml→[tool.ruff], line length 100). - New client methods that read or write real Jira data must never run against a live instance
in tests — mock with
respx.
Shell commands
uv sync --extra dev # install deps (base + dev extras)
uv run pytest # run tests (mocked HTTP only)
uv run pytest --cov=symphony_workflow
uv run ruff check . # lint
uv run ruff format . # format
uv run symphony-workflow list-tools # sanity-check the tool registryREADME, snapshots and CI
README.md (guidance first, reference below ## Reference) and README.zh-TW.md carry real output as snapshots from docs/snapshots/*.txt, marked <!-- snapshot: docs/snapshots/NN-name.txt --> before each code block; tests/test_readme.py checks both languages have the same headings and snapshots and that every block is a verbatim excerpt. The files were captured by running the commands in a throwaway directory with --path . (so no local path appears) against tests/_athena.make_project, a real model for the To-Do proposal, and a throwaway Linear project and Jira tree deleted afterwards; if you re-capture, do the same and re-read the output for names before committing (this repo is public). Each snapshot is shown as a terminal-style SVG in docs/snapshots/img/, rendered from the text by uv run python scripts/render_snapshots.py (re-run it after editing a snapshot: test_readme.py fails on a stale image). 10-first-plan.txt is the first-plan walkthrough, captured with env -i in a clean folder; its Project: line, which prints the absolute path, was replaced by . by hand (the only edit). 09-agent.txt is a real agent session (a Claude agent with plugin/ loaded, tools limited to symphony-workflow commands, settings isolated); capture it again if the skill description changes. CI is .github/workflows/ci.yml (ruff and pytest, Python 3.11 and 3.13) and backs the README's CI badge; the version badge reads the tag, not the release.
Secrets
Real credentials live only in a local, gitignored .env (symphony-workflow init-env creates it from a
template and gitignores it; check-env [--verify] reports which keys are missing without printing a
value — envsetup.py, ADLC-189). Never commit a token, and never let a test read .env — test_cli.py's
test_missing_env_vars_exit_with_helpful_message explicitly clears the relevant env vars via
monkeypatch so the suite stays independent of whatever's in the developer's shell.
Inspecting a directory you do not control
Plan sync reads and writes a project directory that can be someone else's (a cloned repo, a path an agent passes to a tool). Run git only through safepath.run_git (a repo's own .git/config can set core.fsmonitor to a command, and git check-ignore runs it), check every plan file with safepath.is_safe / check_inside before reading or writing it (no symlinks below the root, resolved path inside it), and give agent-facing tools safepath.scoped_path for any path argument. Regression tests: tests/test_plan_sync_security.py (ADLC-270).
What NOT to do here
- Don't wire Implement into the poller.
poller.pyauto-discovers and auto-Plans on purpose, never auto-Implements — that split is the load-bearing safety property of Phase 5 (seedocs/plans/0001-symphony-poc/spec.md), not a missing feature.acceptEditscan't self-verify (documented finding,docs/runs/ADLC-17/review.md); an unattended Plan→Implement chain would ship unreviewed code with nobody watching. The optional triage step (ADLC-29) doesn't change this — it only ever decides plan/skip/flag, code applies the decision, and the triage agent itself gets no Jira write tools, same as Plan/Implement. - Don't add a database or persistent queue casually.
RunStorebeing in-memory-only is a known, documented POC limitation (docs/plans/0001-symphony-poc/spec.md, confirmed in practice onADLC-24) — fixing it is real scope (Symphony §7.4-style recovery), not a quick add. If it's ever tackled, updatedocs/plans/0001-symphony-poc/spec.mdfirst. - Don't hardcode
cloud-f1.atlassian.net, CF1's custom field IDs,ADLC, or a specific target repo path intoclient.py/orchestrator.py/poller.py— they belong in.env/ env vars (SYMPHONY_WORKFLOW_TARGET_REPO,SYMPHONY_WORKFLOW_JIRA_PROJECT, ...), because this is meant to work against any Jira Cloud project and any target repo — seedocs/PORTABILITY.md. - Don't reach for the v3 API / ADF format without a documented reason — it changes every model and client method to be structured instead of plain text.
- Don't assume a correctly-named branch alone gets Jira auto-linking — see the Branch naming section above for the current status of the GitHub-for-Jira app connection this depends on.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

