---
name: flydocs-workflow
description: |
  Unified FlyDocs skill — lifecycle workflow, issue operations, project
  management, knowledge graph, and session context. Tier-aware: auto-detects
  cloud (relay API) or local (filesystem) from config.
triggers:
  - create issue
  - capture issue
  - log a bug
  - add to backlog
  - transition
  - move to
  - mark as
  - set status
  - assign issue
  - close issue
  - start session
  - wrap session
  - project update
  - status update
  - add comment
  - set priority
  - set estimate
---

# FlyDocs Workflow

Index for the FlyDocs development lifecycle, issue operations, project
management, knowledge graph, and session context.

This file tells you what to do next and where to look — it is not the
procedure. Read the stage file for the work in hand, and
`reference/script-catalog.md` for operation IDs and their arguments. Slash
commands (`/capture`, `/implement`, …) load stage files automatically; read them
manually only for natural-language requests that bypass slash commands.

## Lifecycle

```
Capture → Refine → Activate → Implement → Review → Validate → Close
                                  ↕
                               Blocked
```

## Stage Index

| Stage     | File                | Intent                   | Agent          |
| --------- | ------------------- | ------------------------ | -------------- |
| Capture   | stages/capture.md   | Create issue from input  | PM             |
| Refine    | stages/refine.md    | Triage + spec completion | PM             |
| Activate  | stages/activate.md  | Assign + start work      | PM             |
| Implement | stages/implement.md | Build + test + simplify  | Implementation |
| Review    | stages/review.md    | Code quality validation  | Review         |
| Validate  | stages/validate.md  | User acceptance testing  | QE             |
| Close     | stages/close.md     | Archive completed work   | PM             |

Session start, wrap, and stale detection: `session.md`.

## Surfaces: MCP for the Hot Path, `flydocs run` for the Long Tail

The ten operations the lifecycle runs on are MCP tools on the `flydocs` server.
Call them directly. Clients namespace them (`mcp__flydocs__issue_get`); this
skill uses the wire names.

Everything else — audits, milestones, labels, workspace and graph operations —
is the long tail, and runs through `flydocs run <operation-id>`.

Execute, either way. Never describe an operation instead of performing it.

### Invocation Matrix

Each stage file opens its procedure blocks with a **Surface** line naming which
of these carries the step.

| Need                               | Surface | Call                         | Script fallback                                |
| ---------------------------------- | ------- | ---------------------------- | ---------------------------------------------- |
| Read one issue                     | MCP     | `issue_get`                  | `issues.py get REF`                            |
| List issues in focus               | MCP     | `issue_list`                 | `issues.py list --focused`                     |
| Capture an issue                   | MCP     | `issue_create`               | `issues.py create`                             |
| Assign + start work (one call)     | MCP     | `issue_activate`             | `issues.py assign` then `issues.py transition` |
| Move status (comment required)     | MCP     | `issue_transition`           | `issues.py transition REF STATUS COMMENT`      |
| Post a comment                     | MCP     | `issue_comment`              | `issues.py comment REF BODY`                   |
| Tick / untick / defer / note an AC | MCP     | `issue_acceptance_update`    | `issues.py acceptance REF --check N`           |
| Load session continuity            | MCP     | `session_start`              | `session.py start-context`                     |
| End the session (posts the update) | MCP     | `session_wrap`               | `session.py wrap --health H --body-file F`     |
| Mid-session project update         | MCP     | `project_update`             | `session.py project-update --health H`         |
| What shipped around an issue       | MCP     | `change_context`             | none — MCP-only (provenanced issue→PR join)    |
| Everything else                    | runner  | `flydocs run <operation-id>` | the matching dispatcher script                 |

**The scripts still work.** Every rewritten instruction names the script that
does the same job — for one release cycle, and for harnesses with no MCP server
configured. Where both are available, use the tool.

### The Long Tail: `flydocs run <operation-id>`

Sixty-six operations, addressed by a stable ID in `domain.verb` form:
`issue.audit`, `milestone.create`, `workspace.validate`, `graph.query`. The ID
is the contract. It does not name a file, a directory, or an interpreter, so
none of those can break an instruction you have memorised.

```bash
flydocs run issue.audit --limit 20
flydocs run graph.query --node FLY-123 --rel BLOCKS --direction both
flydocs run --list                      # every ID, grouped by domain
```

`--list` is the discovery path and works from anywhere, including a multi-repo
root. For what a single operation accepts, read `reference/script-catalog.md` —
or get it wrong once: a refused invocation prints the accepted shape.

Three properties worth knowing before you use it:

- **No `cd`.** The runner finds the repo that owns `.flydocs/config.json` from
  the working directory, or takes `--repo <name>` in a multi-repo workspace.
  Run it from the workspace root, from a child repo, from any depth inside one.
- **Declared arguments only.** Every flag an operation accepts is registered.
  An unknown flag stops the invocation and prints what was accepted — it is
  never forwarded on the chance the script might understand it.
- **No shell.** Arguments are passed as an argv array. Quoting, `$(…)`, `&&`
  and `;` have no meaning anywhere in this path, so a value containing them is
  a value, not an injection.

`--dry-run` before the ID resolves the entrypoint and prints the argv without
running anything. Use it when you are unsure an invocation is well-formed.

The dispatcher scripts underneath still work when invoked directly; the runner
is a convention over them, not a replacement. `reference/script-catalog.md`
carries both spellings for every operation.

### Structured Arguments: No Temp Files, No Escaping

Tool arguments are structured data. A comment body, a filled wrap template, an
issue description — each is one string argument, and multi-line markdown goes
in as-is. Nothing needs a heredoc, a quoting pass, or a file on disk.

Before — the shell shape, with the body staged on disk (and staged in the wrong
place: `/tmp` is outside the workspace, so nobody can look at what you sent):

```bash
cat > /tmp/ready.md <<'EOF'
**Ready for Review** —

**What changed:** hot-path stage docs now call the tools
**Criteria:** 7/7 complete
EOF
python3 .claude/skills/flydocs-workflow/scripts/issues.py comment FLY-123 < /tmp/ready.md
```

After — `issue_comment`, called with:

```yaml
ref: FLY-123
body: |
  **Ready for Review** —

  **What changed:** hot-path stage docs now call the tools
  **Criteria:** 7/7 complete
```

The `body` is a single string parameter. Write the markdown into it directly.

### Long Inputs on the Runner: `.flydocs/scratch/`

Tools take the markdown inline; `flydocs run` is a command line, and a command
line is the wrong place for a filled wrap template or a rewritten issue
description. Heredocs, escaped newlines and nested quotes in an operation
argument are how a body arrives on the tracker mangled, or how a stray `"`
truncates it silently.

So: **write the body to a file under `.flydocs/scratch/`, pass the path, delete
the file.**

```bash
mkdir -p .flydocs/scratch
cat > .flydocs/scratch/fly-123-description.md <<'EOF'
## Problem

The runner rejects an undeclared flag before it spawns anything.

## Acceptance Criteria

- [ ] Unknown flags exit non-zero
- [ ] The error names what was accepted
EOF

flydocs run issue.description FLY-123 \
  --file .flydocs/scratch/fly-123-description.md \
  --expected-description-hash <HASH>

rm .flydocs/scratch/fly-123-description.md
```

The heredoc writes the _file_. It is never the operation argument — the
argument is a path, which has no quoting problem to have.

`<HASH>` is the `descriptionHash` field `issue_get` returns for the read this
rewrite was based on (it is on the `basic` field set, so the read that gave you
the description gave you the guard too). On the cloud tier a `--file`
description rewrite is **refused** without a guard: a file is text you composed
earlier, and passing the digest is what makes the write fail loudly instead of
overwriting an edit that landed while you were drafting.

`--expected-revision <REVISION>`, where `<REVISION>` is the `revision` field
from that same read, also satisfies the refusal and is still accepted. Prefer
the hash: the revision is a last-modified
timestamp, so it trips on a status change that touched no prose, which is
exactly what you do between reading an issue and rewriting its description
(FLY-1470).

Why this directory and not a system scratch path:

- **Workspace-local.** It sits beside the config the operation runs against, so
  a relative path works from wherever the runner put you.
- **Gitignored.** `.flydocs/scratch/` is in the managed ignore list. Drafts of
  issue content do not belong in a commit.
- **Inspectable.** When an operation refuses a body, the exact bytes you sent
  are still on disk and still readable by the person you are working with.

`--file` is the spelling to reach for. `issue.create` also accepts
`--description-file`, and `session.wrap` and `session.project-update` also
accept `--body-file`; `--file` is an alias of each, so one habit covers all
three. Clean up after a successful call — a scratch directory that accumulates
is a directory nobody trusts.

`issue.update` no longer takes a description at all (FLY-1469). It rewrote the
whole document with no revision token, which is the write `issue.description`
makes under the §8 check — one document, one writer, one guard. Passing
`--description` or `--file` there is refused, and the refusal names the command
above.

## Listing Issues: Focus First

`issue_list` with `focused: true` (script: `issues.py list --focused`) is the
default read — active sprint, else board, else your open assigned work. Focus
narrows attention, it does not restrict access; out-of-focus work stays
reachable on request.

| Use                                   | When                                                     |
| ------------------------------------- | -------------------------------------------------------- |
| `focused: true` / `list --focused`    | Default. What the developer is working on now.           |
| `mine: true, active: true`            | Their wider assigned backlog. Use for counts, not dumps. |
| `project: "<ref>"` / `list --project` | A specific project. Explicit opt-out from focus.         |
| `all: true` / `list --all`            | Everything. Rarely correct; say why you needed it.       |

## Repo Context: Pull, Generate, Push

`flydocs/context/project.md` and `flydocs/context/service.json` normally come
from the server and arrive on `flydocs update` / `context.pull`. A repo the
portal cannot crawl — GitHub Enterprise, self-hosted, or added by clone URL,
where there is no App install — has nothing on the server to pull, and its
context stays empty.

For that case the agent generates and a script pushes:

| Need                             | Surface | Call                       |
| -------------------------------- | ------- | -------------------------- |
| Refresh local context from cloud | runner  | `flydocs run context.pull` |
| Generate context from the repo   | command | `/generate-context`        |
| Push locally generated context   | runner  | `flydocs run context.push` |

`/generate-context` (`.claude/commands/generate-context.md`) pulls first, reads
the repo's manifests and routes, writes both artifacts to the section contract,
and runs the push. Two rules it enforces, and the reason each exists:

- **Only this repo's own narrative is pushed.** The relay serves the rules
  with every read — once under `## Workspace Rules` / `## Repo Rules` /
  `## Status Workflow`, and once unlabelled where the stripped markers used to
  be. The push reads the stored context first and cuts both copies, because
  sending them back duplicates every rule on the next read. A read it cannot
  make is a refusal, not a silent empty push.
- **Generate, push, then `flydocs update`** — never generate then update. An
  update overwrites the local files with the server's copy.

Arguments and refusals: `reference/script-catalog.md`. Descriptor fields:
`reference/service-descriptor-schema.md`.

## Reference

| Topic                               | File                                   |
| ----------------------------------- | -------------------------------------- |
| Operation catalog                   | reference/script-catalog.md            |
| Comment templates                   | reference/comment-templates.md         |
| Status transitions                  | reference/status-workflow.md           |
| Priority & estimates                | reference/priority-estimates.md        |
| PR & git workflow                   | reference/pr-workflow.md               |
| Golden rules                        | reference/golden-rules.md              |
| Provider costs                      | reference/provider-costs.md            |
| Graph schema                        | reference/graph-schema.md              |
| Service descriptor schema           | reference/service-descriptor-schema.md |
| Multi-repo workspaces               | reference/multi-repo.md                |
| CLI config (skipPaths, credentials) | reference/cli-config.md                |
| Writing style (copy/docs)           | reference/writing-style.md             |
| Output formatting (full)            | reference/output-formatting.md         |
| Claude Code hooks                   | reference/claude-hooks.md              |

Issue templates in `templates/issues/` (feature, bug, chore, idea) and PR
templates in `templates/pr/` are pure structure — stage files define how to
fill them.

### For Skill Authors

A skill can add its own operations to the runner: ship an `operations.json`
manifest in the skill directory and its IDs appear in `flydocs run --list`
alongside the core catalog, tagged with the skill they came from. The rules are
the same ones the core catalog lives by — `domain.verb` IDs, every flag
declared, `--repo` reserved — so a contributed operation is reached exactly like
a built-in one and inherits the same standing permission rule.

The manifest schema, the flag kinds the loader accepts, the reserved names and
what a contributed script inherits from the runner are documented in
**Contributing operations from a skill** (`docs/skill-operations.md`). Read it
before writing an `operations.json`: a manifest is validated at load, and an
entry that breaks one of those rules is dropped with a warning on stderr rather
than failing later at the call.

This skill contributes none. Its operations are core, and the note is here so a
skill author looking for the extension point does not go looking in the CLI.

## Golden Rules (Summary)

1. Every status transition gets a comment — a short pointer, not a summary
   (see "Comment discipline" in `reference/comment-templates.md`)
2. Assignment required before In Progress — `issue_activate` does both
3. Checkboxes live in issue description — change them with
   `issue_acceptance_update` (script: `issues.py acceptance`), never by
   rewriting the description
4. Use the tools — execute, don't describe
5. Session wrap posts a project update — `session_wrap` posts it itself
6. Product scope filtering — only show projects/issues matching config labels

For full rules with verification gates, see `reference/golden-rules.md`.
