| name | document |
| description | Jira-driven feature-documentation workflow. Phase 0 preflight-discovers the docs repo + profile (in-repo → built-in dynatrace-docs default → on-demand docs-profile:) and the VI's specs dir under /workspace. Phase 4.5 determines/confirms the applicable space(s). Optional saas|managed constraint scopes the run to one space. Reads a Value Increment hierarchy from exported markdown, resolves PR diffs in parallel, synthesises product documentation, and gates on style-check and Opus doc review. Optional --counterpart <JiraID|PR-url> grounds a space-constrained run on the other space's existing docs (read-only). Activated when the user prompt starts with "document:".
|
| allowed-tools | view, edit, create, bash, glob, grep, task, web_fetch, ask_user |
Generate product documentation for the Jira Value Increment: the argument (text following the document: trigger)
Signature: PRODUCT-NNNN [saas|managed] [--counterpart <JiraID|PR-url>]. The optional second token is a space constraint, not a target list. When you pass saas or managed, the command documents only that space and leaves the OTHER space's rendered output unchanged (SaaS pages stay as they are when you pass managed, and vice-versa). When you omit it, the command determines the applicable space(s) from the Jira hierarchy and the resolved repos, then confirms with you. both is intentionally NOT an accepted value — omit the argument to cover both spaces. --counterpart <JiraID | PR-url> is an optional named flag (valid only on a space-constrained run) that points at the OTHER space's documentation for this feature — a Jira key or a PR URL (merged or not). It is used as read-only grounding; on a both-space run it is rejected. See Phase 5.6.5.
document: (Jira mode) is the Jira-driven feature-documentation workflow. Given a Jira Value Increment key, it reads the full Jira hierarchy from pre-exported markdown in the user's Obsidian vault, resolves PR URLs to local git repos, runs parallel PR-diff summaries, synthesises product documentation, runs style-check + Opus review gates, and writes the output to the current working directory (a product docs repository).
For small one-off doc edits, use direct mode (below). For writing child Epic drafts from a VI, use epics:. For release notes, use release-notes: — this command never writes release-notes / what's-new pages, because those are generated from Jira by the docs team's automation.
Mode detection
document: has two modes, selected by the first argument token:
- Jira mode (Mode A) — the input resolves
jira-driven via the shared front-end (~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/jira-input-resolution.md): a first token matching a JiraID (^[A-Z][A-Z0-9]+-[0-9]+), optionally followed by saas | managed, or a directory that inspects as a Jira-export (contains <KEY>-index.md). The front-end's Fallback B handles a JiraID-shaped token with no jira-products/<KEY> folder.
- Direct mode (Mode B) — the input resolves
direct (a leading @file token, free-text prose, or a non-Jira-export directory, which Mode B handles via its existing "anything else" path).
Echo the detected mode, then proceed to that mode's phases. The two modes share the same docs-style-checker / doc-reviewer / doc-fixer agents (each mode emits its own final report).
Mode A — Jira-driven documentation (JiraID argument)
Phase 0 — Load and dispatch
-
Resolve the Jira input via the shared front-end. Execute
~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/jira-input-resolution.md against
the argument (text following the document: trigger). For Mode A the result is mode: jira-driven with jira_key,
jira_export_root (the ticket export dir — $VAULT_PATH/jira-products/<KEY>
for a JiraID, or the passed directory), source, and specs. The front-end
owns the $VAULT_PATH/jira-products validation and Fallbacks A/B. Carry
jira_key, jira_export_root, focus_key, and specs forward.
-
Resolve the docs repo (cwd-preferred). This command writes feature documentation into a product docs repository; running it outside such a repository is almost always a mistake. The docs signals checked throughout this step are:
package.json with any script matching *:start, *:build, *:lint, docs:*, or
- any of
.docstack/, mkdocs.yml, docusaurus.config.js, antora.yml, .vale.ini, DOCUMENTATION-GUIDELINES.md, or
- a
_snippets/ directory at any level under the repo root.
Resolve docs_repo_path in this order:
- (a) cwd with signals (preserves today's behavior). Run
git rev-parse --show-toplevel from cwd to resolve the git root. If it succeeds and ≥ 1 docs signal is present there → docs_repo_path = that git root and proceed silently. This keeps every downstream phase that assumes cwd correct.
- (b) Search for a dynatrace-docs clone. Else, look under
${REPOS_PATH:-/workspace} (single dir or colon-separated list) for a dynatrace-docs checkout: a top-level directory either named dynatrace-docs, or a git root that contains both dynatrace/_content and managed/docstack.jsonc. If exactly one matches → docs_repo_path = that path. If several match, list them and ask which to use (choices array, recommended first, last item "Other… (describe)").
- (c) Ask. Else, ask:
"No product-docs-repo signals in this working tree and no dynatrace-docs clone found under ${REPOS_PATH:-/workspace}. The signals I checked in cwd:
- package.json scripts matching *:start, *:build, *:lint, docs:*
- .docstack/, mkdocs.yml, docusaurus.config.js, antora.yml, .vale.ini, DOCUMENTATION-GUIDELINES.md
- any _snippets/ directory under the repo root
Where should I write the documentation?"
choices: ["Use cwd anyway — I confirm this is a docs repo (Recommended)", "Enter the docs repo path", "Cancel — switch to a docs repo first", "Other… (describe)"]
"Use cwd anyway" sets docs_repo_path = the git root of cwd (or cwd itself if not a git tree) and carries the user's confirmation forward. "Enter the docs repo path" takes a free-text absolute path and validates it exists.
Confirm writeable. Once docs_repo_path is resolved, run test -w <docs_repo_path>. If it fails, stop with the named error REPO_NOT_WRITEABLE: <docs_repo_path> is not writeable.
-
Recognize dynatrace-docs. Set is_dynatrace_docs = true when the resolved docs_repo_path contains both managed/docstack.jsonc and dynatrace/_content/ and — when a git remote is available (git -C <docs_repo_path> remote get-url origin) — its slug (last path segment, trailing .git stripped) is dynatrace-docs. Directory name alone is not sufficient; the signals decide.
-
Resolve the profile (record profile_source). The profile steers all later phases' conventions. Resolve in this order:
- (a) In-repo profile →
in-repo. If <docs_repo_path>/.dev-workflows/docs-profile.yml exists, load it. profile_source: in-repo.
- (b) dynatrace-docs built-in default →
built-in. Else, if is_dynatrace_docs, load ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/dynatrace-docs/docs-profile.default.yml. profile_source: built-in.
- (c) Custom repo, no profile →
generated. Else (a custom docs repo with no profile), run inline on-demand profiling: invoke the docs-profile: flow against docs_repo_path (Skill tool, skill: "dev-workflows:docs-profile", with docs_repo_path --inline as its arguments — the --inline token tells profiling to skip its branch-naming prompt and standalone PR-draft handoff, since this command owns the single branch + PR draft) and wait for it to write <docs_repo_path>/.dev-workflows/docs-profile.yml. Then load that file. profile_source: generated. If the user cancels profiling (it produces no profile), stop with the named error PROFILE_REQUIRED: a docs-profile is required to write into a custom docs repo; run docs-profile: or switch to a profiled repo.
Hold the loaded profile for later phases.
In-repo-profile-not-on-base guard. When profile_source: in-repo, confirm the profile is committed on the base branch before relying on a docs branch cut from it. Resolve the base (git -C <docs_repo_path> symbolic-ref --short refs/remotes/origin/HEAD; fall back to main, then master) and run git -C <docs_repo_path> cat-file -e <base>:.dev-workflows/docs-profile.yml:
- exit 0 (present on base) → proceed (the common case — the profile was merged earlier).
- non-zero (absent on base) → the profile is only in the working tree / on an unmerged branch, so the docs branch Phase 6.2 cuts from
<base> will not include it. Warn and ask:
choices: ["Proceed — the run uses the in-memory profile; I'll merge the profile PR separately", "Cancel — merge the profile PR first, then re-run", "Other… (describe)"]
Skip this check for profile_source: built-in (no profile file) and generated (the inline profiling branch is adopted by Phase 6.2, so the profile rides the single docs branch — a base check would false-fire).
-
Specs (additive). Use the specs list from the front-end (§Specs
resolution — $SPECS_PATH then the directory case). specs: [] is fine —
specs are additive context for document:; proceed without prompting. For
the downstream phases that scan or cite a single specs location (the Phase 4.5
space hint, the Phase 5.6 image scan, the Phase 5.7 doc-planner dispatch,
and the Phase 5.8 three-way analysis), also record specs_dir = the directory
containing the resolved specs (their common parent — e.g. the
$SPECS_PATH/…/<KEY>…/ folder, or the directory they were found in), or
none when specs is empty.
-
Classify write context for later branch/write decisions — computed against the resolved docs_repo_path (not necessarily cwd). Walk up from docs_repo_path looking for .obsidian/; if found, context = obsidian. Else if git -C <docs_repo_path> rev-parse --show-toplevel succeeds AND at least one docs signal from step 2 is present, context = docs_repo. Else if it succeeds with no docs signals, context = non_docs_repo (step 2 has already asked the user; their confirmation promotes this to docs_repo behaviour). Else context = plain_dir. In a normal run, Phase 0's docs-repo resolution (steps 2–3) yields a real docs repo (docs_repo) or a user-confirmed non_docs_repo; obsidian and plain_dir are defensive guards (they forbid branch/commit) rather than expected write targets.
Record the resolved context — it drives Phase 6.2 (branch setup) and Phase 6.3 write rules. When docs_repo_path differs from cwd, record both and note that the writing phases (Increments 2–3) consume docs_repo_path, not cwd, for every write.
-
Parse the optional space constraint. Read the argument (text following the document: trigger) as <JIRA_KEY> [space] — the same the argument (text following the document: trigger) already split for <JIRA_KEY> in step 1. First set aside any --counterpart <value> flag pair (parsed in step 8) so it is never mistaken for the space token; the space constraint is then the first remaining whitespace-separated token after <JIRA_KEY> (if any).
- No second token →
space_constraint = none. Phase 4.5 will determine and confirm the applicable space(s).
- Second token is
saas or managed (case-insensitive) → space_constraint = <space>. This is a deliberate scoping decision by the user, so Phase 4.5 skips its determination step and records target_spaces = [space_constraint] directly.
- Second token present but not
saas/managed (e.g. both, a typo, or extra free text) → do NOT silently guess. Reject it and ask:
"'<token>' is not a valid space constraint. The constraint scopes the run to a single space; to cover both, omit the argument and let the command determine the applicable space(s). How would you like to proceed?"
choices: ["Drop the constraint — auto-determine (Recommended)", "saas", "managed", "Cancel"]
"Drop the constraint" → space_constraint = none. "saas"/"managed" → space_constraint = <choice>. "Cancel" → stop.
-
Parse the optional --counterpart flag. Scan the argument (text following the document: trigger) for a --counterpart <value> token (named flag; <value> is the next whitespace-separated token, a Jira key ^[A-Z][A-Z0-9]+-[0-9]+ or a URL). Record counterpart_ref = <value> (default null when absent).
--counterpart present but space_constraint == none (both-space run) → do NOT silently accept. Ask:
"--counterpart grounds a single documented space on the OTHER space, so it needs a space constraint (saas|managed). This run covers both spaces. How would you like to proceed?"
choices: ["Drop --counterpart — cover both spaces (Recommended)", "Constrain to saas", "Constrain to managed", "Cancel"]
"Drop" → counterpart_ref = null. "Constrain to saas/managed" → set space_constraint accordingly and keep counterpart_ref. "Cancel" → stop.
--counterpart value malformed (not a Jira key or URL) → do NOT guess. Ask:
"'<value>' isn't a valid --counterpart target. It should be a Jira key (e.g. PROJ-1234) or a PR URL. How would you like to proceed?"
choices: ["Re-enter a valid Jira key / PR URL", "Drop --counterpart (Recommended)", "Cancel"]
"Re-enter" → take the new value. "Drop" → counterpart_ref = null. "Cancel" → stop.
When both the both-space-run rejection and a malformed value apply, resolve the both-space rejection first, then re-validate any value the user keeps.
Readiness
Before clarification, show a readiness table summarizing what Phase 0 resolved:
| Item | Resolved |
|---|
| Jira input | source: <vault | directory>; export root: <jira_export_root> |
| Docs repo | <docs_repo_path> (is_dynatrace_docs: yes/no) — write context <obsidian | docs_repo | non_docs_repo | plain_dir> |
| Profile | profile_source: <in-repo | built-in | generated> |
| Specs | <specs_dir> or none |
| Space constraint | <space_constraint> (saas | managed | none → auto-determine in Phase 4.5) |
| Code repos | resolved later in Phase 4 (slug→clone match under $REPOS_PATH) |
All discovery defaults to /workspace (${REPOS_PATH:-/workspace}); on a host, or when a path is missing, the command asks rather than guessing.
Phase 1 — Clarification
Rule: Ask, don't guess. This rule is absolute.
Group questions where possible; use choices arrays; the last choice in every array MUST be "Other… (describe)".
Ask about:
-
Output filename / sub-path under the resolved docs_repo_path (Phase 0) (default: <KEY>-<slug>.md; the doc-location-finder in Phase 5.5 may override this per target).
-
PR status filter:
choices: ["MERGED only (Recommended)", "All PRs (MERGED + OPEN + DECLINED)", "Specific list (you'll be prompted)", "Other… (describe)"]
-
Repo refresh policy:
choices: ["fetch only (Recommended)", "fetch + pull default branch", "no refresh", "Other… (describe)"]
The fetch only default matches the diff-summarizer default (refresh.fetch: true, refresh.pull: false) — historical PR diffs don't need the current branch tip, and pulling risks moving HEAD away from the merge commit we want to reach.
-
Repos search base ($REPOS_PATH). Read ${REPOS_PATH:-/workspace} (the container mounts every repo under /workspace). $REPOS_PATH may be a single directory or a colon-separated list. Ask:
choices: ["Use $REPOS_PATH (default /workspace) (Recommended)", "Use a different path (you'll be prompted)", "Cancel", "Other… (describe)"]
If "different path", take free-text input (single dir or colon-separated list) and validate that at least one directory exists under it. Record the resolved value as $REPOS_PATH. Individual clones are located in Phase 4 by matching their git remote against each PR's repo slug — not by assuming a <base>/<slug> directory name.
-
Screenshots — ask only whether images are wanted; the candidate list itself is built later in Phase 5.6 (by which point specs_dir, the jira-reader attachments[], and the resolved repos are all available):
choices: ["Yes — include screenshots (you'll pick the sources in Phase 5.6) (Recommended)", "No screenshots needed", "Cancel", "Other… (describe)"]
Record the answer as images_wanted (true/false). When false, Phase 5.6 is skipped and screenshots[] stays empty. The downstream doc-planner (Phase 5.7) detects the repo's image_policy and decides per screenshot whether the writer will copy it locally or stage it for manual upload.
Resolve <screenshot_staging_dir> (only when images_wanted is true). For the cdn_upload_required case the staged copies must live somewhere that survives a container restart — $VAULT_PATH is always host-mounted, the docs repo (often a docker repo-volume) and /tmp are not. Find the ticket's persistent Obsidian project folder:
find "$VAULT_PATH/Projects" -maxdepth 5 -type d -name "<JIRA_KEY>*" 2>/dev/null | head -1
- Found → record the matched folder as
<project_dir> (the project-folder root — reused as an image source in Phase 5.6), and set <screenshot_staging_dir> to that project folder's screenshot subfolder: prefer an existing Doc screenshots/ or Attachments/ subdirectory; otherwise Doc screenshots/ (created on first write).
- Not found (e.g. a non-
PRODUCT- ticket with no project folder) → <project_dir> is null (Phase 5.6's project-folder scan then contributes nothing); ask:
choices: ["Enter an absolute directory under $VAULT_PATH (you'll be prompted)", "Skip — only needed if the docs repo turns out to be cdn_upload_required", "Cancel", "Other… (describe)"]
Reject /tmp and any path inside the docs repo. Record the result as <screenshot_staging_dir> (or null if skipped).
Also display (for user context):
- Resolved cwd absolute path
- Write context (
obsidian / docs_repo / non_docs_repo / plain_dir)
- Whether branching will happen (only when context is
docs_repo — confirmed at plan approval)
- Resolved
$REPOS_PATH
- Resolved
$VAULT_PATH and <JIRA_KEY>
- Space scope — show
space_constraint (Phase 0 step 7): saas/managed means target_spaces is already fixed to that single space; none means the applicable space(s) are auto-determined and confirmed in Phase 4.5 (after the Jira read and repo resolution). Once Phase 4.5 has run, the resolved target_spaces is the authoritative value displayed here.
Phase 1.5 — Classify
Load and follow the model-routing policy at ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/model-routing.md, then classify the task as exactly one of: SIMPLE, MODERATE, SIGNIFICANT, or HIGH-RISK. Jira-driven feature docs are typically SIGNIFICANT (large blast radius if wrong — published documentation). State the classification and a one-sentence reason.
SIGNIFICANT → no separate Opus risk-planner for the high-level plan (the Jira hierarchy + diff summaries are the plan), but doc-planner (Phase 5.7) is pinned to the §2 Opus reasoning chain; the doc-reviewer gate (Opus) is mandatory.
Resolve the per-step routing. Following ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/model-routing.md §9, record a model_routing block (reusing the §4 field names) resolving each model against the fallback chains:
model_routing:
classification: SIGNIFICANT
reason: <one-line>
current_model: <the model this orchestrator is running under>
detection_model: <§2.1 detection chain: claude-sonnet-4.6, fallback claude-sonnet-4.5/gpt-5.4>
planning_model: <§2 powerful chain: claude-opus-4.8 … fallback Sonnet per §2>
review_model: <§2 powerful chain>
implementation_model: <= planning_model>
fixes_model: <= detection_model>
opus_available: <true if a §2 Opus model resolved, else false>
notes: <any §2 / §2.1 fallback or degradation>
Each subagent dispatch below cites which chain it uses (the §9 role→chain map): doc-planner → planning_model; jira-reader, diff-summarizer, doc-location-finder, counterpart-finder, docs-style-checker, doc-fixer, and the Phase 8 maintenance agents → detection_model; doc-reviewer keeps its own frontmatter Opus pin (recorded as review_model, no override added).
Orchestration advisory (window-focused). doc-planner (5.7) and doc-writer (6.3) run on the §2 Opus chain regardless of session; only coordination + the interactive gates (4.5, 5.8 decision, 5.9, 6.1) run on current_model. So:
Phase 2 — Plan + approval
Present a concise plan:
- Resolved
<JIRA_KEY> and the Jira export root <jira_export_root>
- Output filename / path under the resolved
docs_repo_path (from Phase 1)
$REPOS_PATH and the slug→clone resolution for the repos that will be examined (inferred from the jira-reader output in Phase 3; if Phase 3 hasn't run yet, list "TBD — resolved after Jira read")
- PR filter (MERGED only / all / specific)
- Parallelism plan (up to 4
diff-summarizer instances per batch; up to 4 repos per Agent message)
- Write context + whether branching will happen
- Screenshots:
images_wanted (yes/no, from Phase 1). When yes, the candidate list is gathered and confirmed in Phase 5.6 (specs scan + Jira attachments[] + manual paths) — list "candidates resolved in Phase 5.6".
- Target space(s): the resolved
target_spaces ([saas] / [managed] / [saas, managed]). State whether it came from the space_constraint argument (and that the other space's render is left unchanged) or from the Phase 4.5 auto-determination the user confirmed. If Phase 4.5 hasn't run yet (auto-determine, space_constraint = none), list "TBD — determined and confirmed in Phase 4.5".
Ask:
"Documentation plan ready. What would you like to do?"
choices: ["Approve & continue (Recommended)", "Revise plan", "Cancel"]
- Approve → proceed to Phase 3
- Revise → ask what to change, update, re-show, re-ask
- Cancel → stop and summarise what was planned
Phase 3 — Read Jira hierarchy
Invoke jira-reader with depth: full:
→ task(agent_type: "dev-workflows:jira-reader", model: <detection_model — §9 / §2.1 detection chain>):
"Return the structured handoff for this brief:
jira_export_root: [resolved jira_export_root from Phase 0]
jira_key: [resolved <JIRA_KEY>]
depth: full"
Wait for the handoff. If status: NOT_FOUND or status: EMPTY, surface the Jira key dir not found rule in ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/escalation-rules.md (["Re-enter key", "Cancel"]) and act accordingly. On OK, store the handoff for downstream phases.
When focus_key is set (explicit <VI> <Epic>), also derive focus_items = the
focus Epic plus every linked item beneath it (its Stories / Sub-tasks). The
change-scoped phases below consume focus_items in place of the full hierarchy —
Phase 5 (diff summarisation) and Phase 5.7 (doc planning) — while VI-descriptive
phases (e.g. Phase 4.5 space determination) keep the full handoff. When focus_key
is null, every phase uses the full hierarchy exactly as today.
Phase 4 — Resolve repos
From the jira-reader handoff pull_requests list:
- Filter by
status per the Phase 1 PR-status setting (default: MERGED only). This is the pull_requests[].status field, NOT the top-level jira-reader status.
- Group the remaining PRs by
repo (short repo name).
- Build a slug→clone map. For each top-level directory under each entry of
$REPOS_PATH, run timeout 5 git -C <dir> remote get-url origin 2>/dev/null, strip a trailing .git, and take the URL's last path segment as that clone's slug. Skip directories with no .git or whose git remote call fails/times out. Result: <slug> → [<absolute path>, ...].
- Resolve each unique in-scope
repo slug against the map:
- One match — use that absolute path as
repo_path.
- Multiple matches (e.g.
cluster and cluster-repo, both pointing at the same upstream) — auto-prefer basename ending -repo, then _repo/_fast, then alphabetically last; show all candidates at plan approval so the user can override.
- Zero matches — record the slug as missing and defer it to the consolidated repo gate in step 5 (do NOT escalate per slug).
Record each resolution as
repo_slug → repo_path for Phase 5.
- Consolidated repo gate. From step 4, compute
expected = the unique in-scope repo slugs, mounted = those that resolved to a path, and missing = the zero-match slugs. This is the earliest point the repo set is known (it depends on the Phase 3 jira-reader PR links) and it runs before any diff work.
missing is empty — print one line and continue with no gate:
Resolved <M>/<N> repositories from the Jira PRs.
missing is non-empty — present ONE consolidated gate (not one prompt per slug):
This VI's Jira PRs span <N> repositories. <M> mounted, <K> missing:
✓ <mounted slug> ✓ <mounted slug>
✗ <missing slug> (<n> <status> PRs — not found under $REPOS_PATH)
Missing repos are skipped: their code won't be diff-summarised or checked
against the VI's requirements, so the discrepancy analysis will be partial.
choices: ["Mount the missing repo(s) now — I'll wait, then re-scan (Recommended)", "Proceed without them — Jira-only for the missing repos", "Cancel", "Specify a different absolute path for a missing repo", "Other… (describe)"]
Choice semantics follow the Repo unresolved (zero matches) — document: rule in ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/escalation-rules.md, applied to the whole missing set at once:
- Mount now & re-scan (≈ the rule's "I'll clone it — wait") — pause until the user confirms the clones are present under
$REPOS_PATH, then re-run step 3's scan and re-render this gate. Loop until missing is empty or the user picks another option. This is how the operator gets per-repo control: mount whichever repos are available, re-scan, then choose "Proceed" for whatever remains.
- Proceed without them (≈ the rule's "Skip and continue without its PRs") — record every currently-missing repo's PRs as
unresolved, out of scope; continue. Identical downstream state to the previous per-slug skip.
- Cancel — abort the run.
- Specify a different absolute path for a missing repo (≈ the rule's "Specify a different absolute path") — record the given path as that slug's
repo_path, move it from missing to mounted, and re-render.
- If any PRs had
host: other (unsupported host), record them as unresolved and carry them into the Phase 9 report; do not block.
Phase 4.5 — Determine applicable space(s)
Resolve target_spaces — one of [saas], [managed], or [saas, managed]. This is the set of spaces the documentation will cover; the constraint semantics that protect the other space ({{#if project='…'}} conditionals or override-copies + managed/docstack.jsonc ignore allowlisting) are Phase 5.9, so this phase only carries target_spaces forward.
-
If space_constraint is set (saas or managed, from Phase 0 step 7) → set target_spaces = [space_constraint] and skip the determination below. Print:
Constrained to <space_constraint> (the other space's render is left unchanged — see Phase 5.9 techniques).
-
If space_constraint is none → run a first-pass determination from cheap signals already in hand, then confirm with the user:
- Jira text/labels — scan the
jira-reader handoff (VI + linked Epics: summaries, descriptions, labels, components) for explicit "SaaS", "Managed", or "both" mentions. Explicit wording is the strongest signal.
- Resolved-repo leaning — use the Phase-4
repo_slug → repo_path map as a hint, not authority: cluster/Managed-oriented repos (e.g. names containing cluster, managed, server, appliance) lean managed; SaaS-service repos lean saas. A mix of both leans [saas, managed].
- Specs presence/name — if
specs_dir was resolved in Phase 0, a saas/managed hint in its name reinforces the guess; absence is neutral.
Form a best-guess target_spaces from these signals (when they conflict or are silent, default the guess to both saas and managed — under-scoping silently drops a space, which is worse than over-scoping). Confirm with the user, ordering the recommended (auto-detected) option first:
"Determined applicable space(s): <auto-detected> — from [signals that drove it]. Confirm or override:"
choices: ["<auto-detected> (Recommended)", "saas only", "managed only", "both saas and managed", "Other… (describe)"]
Map the confirmed choice to target_spaces: "saas only" → [saas], "managed only" → [managed], "both saas and managed" → [saas, managed]; "Other… (describe)" takes free text and resolves to one of the three. Record the confirmed target_spaces.
Phase 4.5's confirmed value is authoritative — no later phase re-derives target_spaces from the full diff/spec analysis; it threads through unchanged into Phase 5.7 (doc-planner) and the writing phases.
Phase 5 — Parallel diff summarisation
Spawn diff-summarizer instances in batches of up to 4 concurrent agents per Agent message. Wait for each batch to complete before spawning the next. If fewer than 4 repos remain, the final batch is smaller.
Rationale: Copilot CLI's practical parallel-subagent limit is ~4–5; going above that causes silent serialisation or rate-limiting. Capping at 4 makes runtime deterministic.
For each repo, in the same Agent message:
→ task(agent_type: "dev-workflows:diff-summarizer", model: <detection_model — §9 / §2.1 detection chain>):
"Summarise this repo's PRs for the brief:
repo_path: <resolved absolute path for this repo from Phase 4>
repo_url_slug: <repo slug, e.g. "cluster">
pr_refs: [ ... full PR entries from jira-reader handoff, filtered to this repo (and, when focus_key is set, to focus_items) ... ]
context: |
[1–2 sentences: VI goal + themes relevant to this repo]
jira_keys_hierarchy:
[VI key + every linked_items key from jira-reader; when focus_key is set, restrict to focus_items — the focus Epic + its linked descendants]
refresh:
fetch: true
pull: [false if Phase 1 chose 'fetch only' (default) or 'no refresh'; true if 'fetch + pull default branch']"
After the batch returns, handle each per-repo status:
OK / PARTIAL — store the output, continue.
REPO_MISSING — should not happen at this stage (Phase 4 already checked). If it does, escalate per the Repo missing (after resolution) rule in ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/escalation-rules.md.
DIRTY_TREE — escalate:
choices: ["Stash changes and retry this repo", "Skip this repo", "Cancel", "Other… (describe)"]
REFRESH_BLOCKED — escalate:
choices: ["Continue with current local state", "Skip this repo", "Cancel", "Other… (describe)"]
NO_PRS_RESOLVED — record all that repo's PRs as unresolved; continue.
After every batch completes, if every PR across every repo is unresolved, present a single aggregate gate (not per-PR):
choices: ["Proceed with Jira-only content (Recommended — writer/planner draw from jira-reader output; final report notes missing PR content)", "Review candidates one by one", "Cancel"]
Phase 5.5 — Find documentation locations
Invoke doc-location-finder:
→ task(agent_type: "dev-workflows:doc-location-finder", model: <detection_model — §9 / §2.1 detection chain>):
"Find write target(s) for the brief:
repo_root: [the resolved docs_repo_path (Phase 0)]
feature_summary: [2–4 sentences combining jira-reader themes + value_increment.goal]
diff_highlights: [key filenames / symbols from the diff-summarizer per_pr summaries]"
Handle the return:
status: OK with a populated targets list:
choices: ["Accept all proposed locations (Recommended)", "Adjust individual locations (you'll be prompted per item)", "Cancel"]
status: LOW_CONFIDENCE — display confidence_notes alongside the targets so the user sees what was ambiguous:
choices: ["Adjust individual locations (Recommended)", "Accept all proposed locations", "Cancel"]
(The default flips to "Adjust" because confidence is low.)
status: EMPTY — skip the accept/adjust flow:
choices: ["Specify locations manually (you'll be prompted)", "Cancel"]
The manual path takes a free-text entry per target (path + kind + section) and validates path existence for extend-existing targets.
The confirmed target list (from any of the three paths above) is the authoritative write-target set for Phase 6.3 and is handed to doc-planner in Phase 5.7.
Phase 5.6 — Image candidates
Skip this phase entirely when images_wanted is false (Phase 1) — screenshots[] stays empty and Phase 5.7 receives no images.
When images_wanted is true, build a merged, deduped candidate list from four sources (by this point Phase 0's specs_dir, the Phase 1 <project_dir>, the Phase 3 jira-reader attachments[], and the Phase 4 resolved repos are all in hand):
- Recursive scan of
<specs_dir> — when Phase 0 resolved a specs_dir (not none), recursively scan it for image files across the spec root, epics/, and spec/:
find "<specs_dir>" \( -path "*/epics/*" -o -path "*/spec/*" -o -path "<specs_dir>/*" \) \
-type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.gif" -o -iname "*.svg" -o -iname "*.webp" \) 2>/dev/null
When specs_dir is none, this source contributes nothing.
jira-reader attachments[] — the image paths enumerated under the VI's attachments/ dirs (Phase 3 handoff attachments[].path; these live under jira-products/<VI-dir>/…, so this covers screenshots developers attached in Jira). May be empty.
- Recursive scan of
<project_dir> — when Phase 1 resolved a <project_dir> (the persistent Obsidian project folder under $VAULT_PATH/Projects), recursively scan it for image files:
find "<project_dir>" -type f \
\( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.gif" -o -iname "*.svg" -o -iname "*.webp" \) 2>/dev/null
This surfaces images you keep in the project folder (custom diagrams, curated screenshots). When <project_dir> is null, this source contributes nothing.
- Manual paths — the user-provided "I'll provide screenshot paths" option (free text, see the
choices below).
Dedupe by resolved absolute path (collapse mixed separators / trailing-slash differences); when the same image appears in more than one source, keep one entry and note its origins. Present the deduped candidates, then ask:
"Found <N> candidate image(s): <count> from specs scan, <count> from Jira attachments, <count> from the project folder. How would you like to source screenshots?"
choices: ["Use all auto-discovered + add manual paths (Recommended)", "Use all auto-discovered only", "Select a subset (you'll pick per candidate)", "Provide screenshot paths manually only (you'll be prompted)", "No images after all", "Other… (describe)"]
- Use all auto-discovered + add manual → take every deduped candidate, then prompt for additional free-text absolute paths to append.
- Use all auto-discovered only → take every deduped candidate; no manual prompt.
- Select a subset → present the deduped candidates and let the user pick which to keep.
- Provide screenshot paths manually only → ignore the auto-discovered candidates; take free text only.
- No images after all → set
images_wanted = false semantics for this run; leave screenshots[] empty and skip the rest of this phase.
For any manual free-text paths, accept any absolute filesystem path (vault, /tmp, home, the docs repo); accept multiple (one per line or space-separated). Validate each path exists and has an image extension (.png|.jpg|.jpeg|.gif|.svg|.webp); drop and report any that don't.
When you need to add a new image for this feature (a screenshot the docs should have but no source yet holds), place it in the Projects VI-dir — <project_dir> (i.e. $VAULT_PATH/Projects/<VI-dir>/…, e.g. its Doc screenshots/ subfolder). Never put it under jira-products/: that directory is regenerated on every Jira import, so a manually-added image there is lost on the next import. jira-products is a read-only source (developer-attached Jira screenshots, via source 2); authored/curated images belong in the Projects folder.
The selected paths populate the existing screenshots[] passed to doc-planner in Phase 5.7 — the downstream placement machinery (per-screenshot dest/staging/upload_note, image_policy) is unchanged.
Phase 5.6.5 — Counterpart-space reference discovery
Run only when target_spaces is a single space ([saas] or [managed]). A run that already covers both spaces has no "other" space → skip entirely and carry counterpart_references = []. (A --counterpart on a both-space run was already resolved in Phase 0.)
The counterpart space is the one space in profile.spaces[] not in target_spaces. Discover its existing documentation for this feature and carry it forward as read-only grounding — concepts, terminology, facts, section structure, and comprehension-only screenshots. It is never copied into the target doc and never an image source (Phase 5.6 remains the only image source).
Invoke counterpart-finder:
→ task(agent_type: "dev-workflows:counterpart-finder", model: <detection_model — §9 / §2.1 detection chain>):
"Discover counterpart-space grounding:
repo_root: [docs_repo_path]
target_space: [the single member of target_spaces]
counterpart_space: [the profile.spaces[] space not in target_spaces]
profile: [Phase 0 profile]
feature_summary: [2–4 sentences from the jira-reader themes + VI goal]
jira_key: [<JIRA_KEY> (focus_key when set)]
counterpart_ref: [the --counterpart value from Phase 0, or null]
diff_highlights: [key filenames/symbols from the Phase 5 diff summaries, optional]"
Handle the result:
status: EMPTY → set counterpart_references = []; print the notes line and proceed to Phase 5.7 unchanged.
status: ERROR → log the reason, set counterpart_references = [], proceed (never block).
status: OK → present the candidates and confirm (always ask; pre-select the match_confidence: high rows):
"Found <N> counterpart-space page(s) to ground on:
| # | Page | Space | Confidence | Already in <target> render? | Why |
...
How should I use these?"
choices: ["Ground on the recommended (high-confidence) set (Recommended)", "Pick a subset (you'll choose)", "Provide a --counterpart ref instead (you'll be prompted)", "Skip grounding", "Other… (describe)"]
- recommended set → keep the
high rows.
- subset → user picks rows.
- provide a ref → take a free-text Jira key / PR URL, re-invoke
counterpart-finder with counterpart_ref set, then confirm again.
- skip →
counterpart_references = [].
Shared-page signal. For any kept reference with is_shared_into_target: true, tell the user the target space may already be covered by that page (its render is pulled in via cross_space_override) — the work may be a small {{#if project='<target>'}} delta, not a new page. Carry this into Phase 5.7 (feeds the planner's write-strategy recommendation) and Phase 5.9.
Record the confirmed counterpart_references[]. It threads into Phase 5.7 (doc-planner) and Phase 6.3 (doc-writer); it never alters the Phase 5.6 image set.
Phase 5.7 — Plan the documentation
Invoke doc-planner:
→ task(agent_type: "dev-workflows:doc-planner", model: <planning_model — §9 / §2 Opus chain>):
"Produce the documentation checklist for the brief:
jira_reader_handoff: [paste full YAML from Phase 3; when focus_key is set, restrict linked items to focus_items]
diff_summaries: [paste array of diff-summarizer outputs from Phase 5]
write_targets: [paste confirmed list from Phase 5.5]
screenshots: [selected candidate paths from Phase 5.6, possibly empty]
screenshot_staging_dir: [resolved <screenshot_staging_dir> from Phase 1, or null]
repo_root: [the resolved docs_repo_path (Phase 0)]
code_repos: [the Phase-4 resolved {slug, path} map; [] if none resolved]
specs_dir: [resolved <specs_dir> from Phase 0, or null]
profile: [the docs-profile loaded in Phase 0 — drives space routing + the multi-space write strategy]
target_spaces: [the resolved target_spaces from Phase 4.5: [saas] | [managed] | [saas, managed]]
counterpart_references: [the confirmed counterpart_references from Phase 5.6.5; [] when none]"
Handle the status and gaps:
status: OK, gaps: [] → proceed to the approval prompt.
status: OK or PARTIAL with gaps entries — for each gap, act on its recommended_action:
"ask user" → prompt inline before showing the checklist-approval choice. Free-text prompt scoped to the gap; feed the answer back to the planner via a single re-invocation (pass the user's answer as an additional gap_resolution field in the brief). If the user declines, fall back to "mark TODO in draft".
"mark TODO in draft" → surface in the checklist display as a visible TODO; the writer at Phase 6.3 emits <!-- TODO: … --> markers. Does not block approval.
"skip with note in final report" → list in the checklist display; carry forward into the Phase 9 ### Skipped items. Does not block approval.
status: PARTIAL alone (without user-asked gaps) is presented to the user alongside the checklist so the approval decision is informed.
Present the checklist (with any gaps + dispositions, and — when the planner returned a non-empty repo_authoring_guidance — the repo-specific authoring rules it extracted from the repo's own guidance files, so the user sees "this repo's CONTRIBUTING.md / copilot-instructions.md requires …" before approving):
choices: ["Approve & write (Recommended)", "Adjust (describe)", "Cancel"]
Phase 5.8 — Discrepancy analysis & user decision
Run this phase when the doc-planner handoff contains any verification_warnings with finding: CONTRADICTED, NOT_FOUND, AMBIGUOUS, or verdict SPEC-VS-JIRA. If there are none, skip to Phase 6.3.
This phase is three-way when a spec was provided (Phase 0 resolved specs_dir and Phase 5.7 passed it to doc-planner): it compares the Jira narrative, the Spec (authoritative "intended"), and the Code ("actual"), per ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/source-truth.md §7. When no spec was provided, the planner emits spec_phrasing: "(no spec)"; the Spec phrasing column simply renders (no spec) and the run behaves exactly as the original Jira-vs-code two-way protocol.
-
Present the analysis table (informational, before asking):
| # | Claim | Jira phrasing | Spec phrasing | Source (code) phrasing | Source location | Verdict |
One row per warning. The Spec phrasing cell reads (no spec) when no spec was provided. Use Source (code) phrasing: "(not verifiable)" for no-source-evidence entries. The Verdict carries the §4.2 finding (CONTRADICTED, NOT_FOUND, AMBIGUOUS) or SPEC-VS-JIRA (the spec differs from the Jira narrative; recommended action "Document as intended (spec)").
-
Batch decision:
choices: ["Decide per discrepancy (Recommended)", "Document ALL as intended (spec)", "Document ALL as actual (code)", "Skip ALL and report (drafts a bug report)", "Cancel", "Other… (describe)"]
"Document ALL as intended (spec)" uses the spec_phrasing (or the Jira phrasing when it is (no spec)).
-
Per-discrepancy (if "Decide per discrepancy"): for each warning, show claim + Jira phrasing + Spec phrasing + Source (code) phrasing + location, then:
choices: ["Document as intended (spec)", "Document as actual (code)", "Skip this claim and report it", "Cancel", "Other… (describe)"]
"Document as intended (spec)" describes the agreed contract — the spec_phrasing (or the Jira phrasing when it is (no spec)) — and, when the code lags the intended phrasing, adds an intentional-discrepancy marker + bug-report draft. "Document as actual (code)" matches what shipped. "Skip this claim and report it" omits the claim but still records the gap in the bug-report draft.
-
Record discrepancy_decisions[] keyed by number (claim, jira_phrasing, spec_phrasing, source_phrasing, source_location, decision ∈ {document-as-spec, document-as-code, skip-and-report}, rationale). spec_phrasing is recorded verbatim ((no spec) when none was provided). Set bug_report_destination to the ticket's vault project folder (resolved exactly like the release-notes destination in release-notes: — find $VAULT_PATH/Projects -maxdepth 5 -type d -name "<JIRA_KEY>*"; ask if none) when any decision is document-as-spec (where the code lags the intended phrasing) or skip-and-report.
Pass discrepancy_decisions to Phase 6.3.
Phase 5.9 — Write-strategy approval (multi-space safety)
Run this phase when the doc-planner checklist contains any target whose
write_strategy.strategy is conditional or override-copy (i.e. at least one
shared page needs cross-space protection). If every target is plain, skip to
Phase 6.3 — there is nothing to protect.
The mechanics are defined in
~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/dynatrace-docs/multi-space-writing.md; this
phase only confirms the per-page strategy choice before Phase 6.3 writes.
-
Present the recommended strategies (informational, before asking) — one
row per non-plain target:
| # | Page (target_path) | space_scope | rendered_in | Recommended | For space | Rationale |
Recommended is write_strategy.strategy; For space is write_strategy.target_space. Remind the user of the invariant: a conditional edits the shared file in place but leaves the protected space's render unchanged; an override-copy duplicates the page into the other space and allowlists it in cross_space_override's ignore.
-
Batch decision:
choices: ["Accept all recommended (Recommended)", "Decide per page", "Cancel", "Other… (describe)"]
-
Per page (if "Decide per page"): for each non-plain target, show the row and:
choices: ["Conditional — edit shared page in place ({{#if project='…'}})", "Override-copy — separate page in the other space + docstack ignore", "Cancel", "Other… (describe)"]
The default/recommended choice is the planner's write_strategy.strategy, listed first.
-
Record write_strategies[] keyed by target_path: {target_path, strategy ∈ {conditional, override-copy, plain}, target_space, rationale}. Targets the planner marked plain are carried through as plain without prompting. Pass write_strategies to Phase 6.3.
Phase 6.1 — CDN image handoff
Run this phase only when, in the Phase 5.7 doc-planner return, any screenshot has image_policy: cdn_upload_required — or the user picked "Stage for manual upload" under an ambiguous target in Phase 6.3. (When the only image policy in play is local, skip this phase: local images are copied into the repo at Phase 6.3 with no handoff needed.)
-
List each affected image so the decision is informed — one row per image:
- target page / anchor it belongs on (from the planner's per-screenshot placement);
- proposed alt text;
- the planner's
upload_note.
-
Ask how to handle the upload:
choices: ["Upload now — I'll paste the CDN links (Recommended)", "Defer — stage with TODO placeholders + Phase 9 list", "Cancel", "Other… (describe)"]
- Upload now → collect one CDN URL per image (prompt per image, or one URL per line in image order). Validate each pasted value looks like a URL (e.g. starts with
http:// / https://); re-prompt for any that don't. Record cdn_urls[<image>]. Phase 6.3 then writes the real CDN URL into each markdown image reference instead of a TODO placeholder. Nothing is staged and the Phase 9 "Screenshots to upload manually" section stays empty for these images.
- Defer → the existing async behavior: stage each image under
<screenshot_staging_dir> (the ticket's persistent Obsidian project folder resolved in Phase 1), Phase 6.3 inserts the TODO-upload placeholder reference, and every staged image is listed in the Phase 9 ### Screenshots to upload manually section.
- Cancel → stop and summarise.
Record the decision as cdn_handoff_decision ∈ {upload-now, defer} and carry it (with any cdn_urls) into Phase 6.3.
Phase 6.2 — Branch setup (conditional)
Run this phase only when write context = docs_repo (or non_docs_repo after user confirmed at Phase 0 step 2) AND the user confirmed branching at plan approval. Never for obsidian or plain_dir.
-
Update the base branch. Resolve the default branch by running git symbolic-ref --short refs/remotes/origin/HEAD; this returns the remote's default (main or master; legacy repos frequently still use master). If the command fails (unset origin/HEAD), run git remote set-head origin --auto and retry; if it still fails, try main, then master, in that order. If the user picked a release/* branch earlier in Phase 1, use that instead. Once the base is resolved: git fetch origin. Then update the base working copy only outside the inline-profiling case: when profile_source is NOT generated, git switch <base> && git pull --ff-only. In the inline-profiling case (profile_source: generated), do NOT switch — HEAD must stay on the generated profile branch so step 5's git branch -m <name> renames that branch (the profile branch was created off the base in Phase 0, so it is already current). When a switch happened and the fast-forward pull fails:
choices: ["Stash local changes and continue (Recommended)", "Proceed from current base state", "Cancel"]
-
Clean-tree check. git status --porcelain; if non-empty:
choices: ["Stash changes and continue (Recommended)", "Proceed anyway — pre-existing changes will appear in the diff", "Cancel"]
-
Derive branch name from repo conventions. In priority order, look at repo root for CONTRIBUTING.md, CONTRIBUTION.md, README.md, DOCUMENTATION-GUIDELINES.md. Grep each for a branch-naming section (case-insensitive, patterns like "Branch name", "Branch naming", "naming your branch"). If a pattern like <user>/<JIRA-KEY>-<slug> or <prefix>/<name> is documented, derive the branch name by filling placeholders with known values (Jira key from Phase 0, slug from the feature summary, <user> from git config user.name or its initials). If multiple patterns are documented, offer them all to the user.
-
Confirm the branch name — always, even when derived from conventions (initials and slugs are subjective):
choices: ["Use proposed name: <name>", "Edit name (you'll be prompted)", "Cancel"]
Fallback default when no convention is found: docs/<jira-key>-<slug>.
-
Create or adopt the branch, and record handoff anchors. Record base_branch = the base resolved in step 1 (the Phase 8.5 squash uses it).
- Normal case (
profile_source is in-repo or built-in, or a custom repo whose profiling did not create a branch): git switch -c <name> from base_branch.
- Inline-profiling case (
profile_source: generated): Phase 0's docs-profile: already ran git switch -c <profile-branch> and committed .dev-workflows/docs-profile.yml, so HEAD is already on that branch. Do NOT create a new branch — rename it with git branch -m <name>. Record profile_commit = the commit that introduced the profile config: git log --diff-filter=A --format=%H -- .dev-workflows/docs-profile.yml | head -1. Phase 8.5 squashes the docs commits onto profile_commit, keeping the profile-config commit as a distinct first commit. (Per ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/finish-and-handoff.md §1.)
No external CLI calls; all git operations are local.
Phase 6.3 — Write documentation
The writing is delegated to the doc-writer subagent (pinned to the §2 Opus reasoning chain — see ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/model-routing.md §9.2). The orchestrator prepares a structured handoff and dispatches; it does not write pages itself.
-
Write the handoff file. Create a temp file (mktemp, e.g. $(mktemp -t dw-<JIRA_KEY>-XXXX.yml) — never the vault, never the docs repo) containing the doc-writer input contract: jira_reader_handoff, diff_summaries, write_targets, doc_planner_checklist (+ gap dispositions), repo_authoring_guidance (the planner's extracted repo-specific rules), discrepancy_decisions (Phase 5.8), write_strategies (Phase 5.9), cdn_handoff_decision + cdn_urls + screenshot_staging_dir + screenshots (Phase 6.1), target_spaces, profile, docs_repo_path, counterpart_references (Phase 5.6.5), and bug_report_destination. Record its absolute path.
-
Dispatch the writer:
→ task(agent_type: "dev-workflows:doc-writer", model: <planning_model — §9 / §2 Opus chain>):
"Write the product documentation for this brief.
handoff_file: [absolute path of the temp handoff file from step 1]"
- Handle the return.
Write context governs branch/commit (Phase 0 step 6); the orchestrator commits the writer's output (the writer never commits):
| Write context | Branch | Commit |
|---|
obsidian | NEVER | NEVER |
docs_repo | YES (opt-in confirmed at plan approval) — see Phase 6.2 | YES (orchestrator commits doc-writer's files_written) |
non_docs_repo | Phase 0 step 2 already asked user to confirm; if confirmed, behave as docs_repo | YES (if user confirmed at Phase 0) |
plain_dir | NEVER | NEVER |
Phase 6.4 — Style check (before reviewer)
Mandatory: the orchestrator MUST dispatch docs-style-checker and act on its return — never skip on its own judgement of which linters are installed.
docs-style-checker runs the chain internally: the repo's primary linter (Vale, etc.) AND — when the dt-style-guide plugin is installed — dt-style-checker as a complementary semantic / cross-page-consistency pass, merging and deduping both finding sets. The two are complementary, not redundant (Vale: lexical at scale + frontmatter; dt-style-checker: engineer jargon, cross-page label consistency, subject-verb agreement, plural/singular label mismatch). The command does NOT invoke dt-style-checker separately — the agent already did.
Invoke docs-style-checker on the files written in Phase 6.3:
→ task(agent_type: "dev-workflows:docs-style-checker", model: <detection_model — §9 / §2.1 detection chain>):
"Run the style check for this brief:
repo_root: [the resolved docs_repo_path (Phase 0)]
files: [absolute paths of every file written or modified in Phase 6.3]"
Act on the return:
-
status: NOT_CONFIGURED — neither a repo linter NOR the dt-style-checker complementary pass was available (the agent already tried both). Proceed to Phase 7; doc-reviewer will still check correctness/completeness.
-
status: OK — the chain ran (primary and/or complementary), zero merged violations. Proceed to Phase 7.
-
status: VIOLATIONS_FOUND — invoke doc-fixer with the violations treated as per their severity. After doc-fixer completes, re-run the linter once:
→ task(agent_type: "dev-workflows:doc-fixer", model: <detection_model — §9 / §2.1 detection chain>):
"Fix the style violations for this brief:
Task description: [doc writing for <JIRA_KEY>]
Reviewer or style-checker output: [paste full docs-style-checker output]
Project root: [the resolved docs_repo_path (Phase 0)]
Severities to fix: BLOCKER and MAJOR"
If violations remain after the re-run:
choices: ["Proceed to review anyway — reviewer may still PASS", "Show remaining violations and let me fix manually", "Cancel"]
-
status: ERROR — surface the error reason and ask:
choices: ["Proceed to doc-reviewer (style check unavailable — doc-reviewer still runs)", "Cancel and fix locally"]
Phase 6.5 — Render verification
Run this phase after Phase 6.4 only when Phase 6.3 wrote files into a buildable docs repo (write context docs_repo, or non_docs_repo confirmed at Phase 0). Skip for obsidian / plain_dir (nothing was written into a repo that builds). Mechanics: ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/dynatrace-docs/render-verification.md. "Affected pages" = every file written or modified in Phase 6.3.
Step 1 — Build check (gating)
Run profile.commands.build if the profile defines one. Do NOT re-run the Phase 6.4 prose linter. Classify any failure:
- Content failure (Handlebars won't compile, unresolved snippet include, broken postid/internal link, malformed conditional) → invoke
doc-fixer (Severities: BLOCKER and MAJOR), then re-run the build once. If failures remain:
choices: ["Proceed to smoke-check anyway", "Show remaining and fix manually", "Cancel"]
- Environmental failure (the build tool will not run — missing toolchain,
command not found, missing .docstack shim) → surface the reason; no doc-fixer loop:
choices: ["Proceed (build unverified)", "I'll fix locally — retry the build", "Cancel"]
When the profile defines no build command (the dynatrace-docs case), record "no build command in profile; build proof deferred to the dev-server boot (Step 2)" and proceed.
Step 2 — Dev-server smoke-check (opt-in, best-effort)
Offer it:
choices: ["Run smoke-check (Recommended)", "Skip — use the manual table only", "Cancel"]
When run, for each space in target_spaces, sequentially (profile.dev_servers.concurrent: false forbids overlap) — full mechanics in render-verification.md:
- Prerequisites (best-effort, never auto-applied). Verify
profile.prerequisites. The .docstack shim is a local, gitignored dev-environment workaround — check it, NEVER apply it. Unmet → record "smoke-check skipped for <space>: prerequisite <x> unmet" and use the manual table for that space.
- Boot
profile.dev_servers.servers[<space>].command in the background; record the process id.
- Readiness poll — GET
http://localhost:<port><base_path>/ until HTTP 200 or profile.dev_servers.readiness_timeout_seconds seconds (fall back to 120 when absent). On timeout → stop the process, record "smoke-check skipped for <space>: not ready", use the manual table for that space.
- For each affected page rendered in
<space>, GET its derived URL (Step 3 route rule) → assert HTTP 200.
- For each cross-space page (its
write_strategy.strategy is conditional or override-copy), grep the rendered HTML for the page's delta marker (render-verification.md §4): PRESENT when <space> is the strategy's target_space, ABSENT when <space> is the protected space.
- Stop the server (kill the recorded process id) before the next space.
Outcomes:
Step 3 — "Pages to visit" table (always)
Emit a table, one row per affected page — URL per space the page renders in (http://localhost:<port><base_path>/<route>; blank for a space the page does not render in), the page's write_strategy.strategy, and what to verify (cross-space: "confirm <target_space> shows the change and the <protected_space> render is unchanged"; plain: "confirm the page renders as intended"). When the smoke-check ran, annotate each cell ✅ 200 / ⚠️ skipped (reason) / ❌ failed.
Route derivation (best-effort): <route> = the page path relative to its space's content_root with a trailing index.md/.md removed. Approximate — a wrong route that 404s in Step 2 simply downgrades that page to the manual table.
Carry the table and the Step 1/Step 2 outcomes into the Phase 9 ### Render verification section, and pass a one-paragraph render_verification summary to Phase 7.
Phase 7 — Doc review gate
Invoke doc-reviewer (Opus — pinned by its own frontmatter; recorded as review_model, no dispatch override added). The reviewer is product-docs-only; Epic drafts go through epic-reviewer in epics:.
→ task(agent_type: "dev-workflows:doc-reviewer"):
"Review the written product documentation for this brief:
Task description: [one-paragraph summary of the feature and <JIRA_KEY>]
Written doc file paths: [absolute paths of every file written in Phase 6.3]
Jira directory path: [<jira_export_root>]
Diff summaries: [array of diff-summarizer outputs from Phase 5]
doc-planner checklist: [the full YAML from Phase 5.7]
style-check report: [the violations output from Phase 6.4 — from docs-style-checker or dt-style-checker (fallback), or 'status: NOT_CONFIGURED' if neither ran]
render_verification: [the Phase 6.5 summary — build result; smoke-check per space (passed / skipped with reason); cross-space invariant check result]
code_repos: [the Phase-4 resolved {slug, path} map; [] if none resolved]
counterpart_references: [the confirmed counterpart_references from Phase 5.6.5; [] when none — supplies the screenshots_seen provenance and the grounded counterpart space for the 'Cross-space grounding integrity' dimension]
target_spaces: [the resolved target_spaces from Phase 4.5]"
Act on the verdict:
-
BLOCK — invoke doc-fixer with Severities to fix: BLOCKER and MAJOR. Re-invoke doc-reviewer once. If the second verdict is still BLOCK, escalate for each unresolved BLOCKER individually per the Review verdict BLOCK (unresolved after one fix cycle) — document: rule in ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/escalation-rules.md:
choices: ["Provide manual fix notes (you'll be prompted)", "Defer to a follow-up issue (record in Phase 9 report)", "Override and accept the finding", "Cancel the whole run"]
"Manual fix notes" → take free-text from the user; apply via doc-fixer in a bounded one-shot pass (no further re-review cycle). "Defer" → record in Phase 9 ### Deferred items without an override flag. "Override" → record in ### Deferred items with the user's rationale. "Cancel" aborts.
-
PASS WITH RECOMMENDATIONS — invoke doc-fixer for MAJOR findings only:
→ task(agent_type: "dev-workflows:doc-fixer", model: <detection_model — §9 / §2.1 detection chain>):
"Fix the review findings for this brief:
Task description: [doc writing for <JIRA_KEY>]
Reviewer or style-checker output: [paste full doc-reviewer output]
Project root: [the resolved docs_repo_path (Phase 0)]
Severities to fix: BLOCKER and MAJOR"
MINOR / NIT findings are deferred to the Phase 9 report.
-
PASS — proceed to Phase 8.
Cap: one fix cycle + one re-review maximum.
Phase 8 — Post-implementation maintenance
First gather the change context:
a. Run git -C <docs_repo_path> diff --stat against the base branch (if branching happened at Phase 6.2) or against HEAD (if no branching) and capture the list of changed files.
b. Compose a change summary block:
Implementation: [one-sentence description of what was documented, naming <JIRA_KEY>]
Change type: docs
Classification: SIGNIFICANT
Files changed (from git diff --stat):
<paste the git diff --stat output>
Notable additions/removals: [new pages, new sections, new snippets, new cross-links, restructured navigation — one line each; or "none"]
Doc-review verdict: [PASS | PASS WITH RECOMMENDATIONS | BLOCK]
Then spawn all four Phase 4-style maintenance agents in a single Agent message. They are independent and run concurrently.
Agent 1 — Documentation (general-purpose, model: <detection_model — §9 / §2.1 detection chain>):
"Post-write documentation review. Change summary:
[paste change summary block]
Scan for README.md, CHANGELOG.md, docs/, or any .md files in the project root or an adjacent docs tree.
Determine if other documentation needs updating as a consequence of this write (e.g., an index page, a cross-referenced overview, a changelog entry in the repo root). Do NOT touch release-notes / what's-new pages — those are generated from Jira by automation.
- Skip if: the edit is confined to the intended target pages with no inbound cross-references.
- Update if: new page requires an index/sidebar entry, new sections require inbound cross-links.
If an update is warranted: apply minimal edits to the relevant section(s).
Return: file updated and what changed, OR 'no update required (reason)'."
Agent 2 — Knowledge base (general-purpose, model: <detection_model — §9 / §2.1 detection chain>):
"Post-write knowledge review. Change summary:
[paste change summary block]
Check ~/.copilot/memory/ (global) and .copilot/memory/ (project-level, preferred for repo-specific knowledge) for existing knowledge files.
Determine if a new knowledge entry is warranted — look for: reusable insights about this docs repo's conventions, non-obvious style rules uncovered, Vale / lint interactions, snippet patterns, image-policy discoveries.
If YES: append to the most appropriate existing file (never create a new file if an existing one fits) using this format:
[Short title]
- Context: what problem/situation triggered this
- Insight: the learned rule, pattern, or gotcha
- When it applies: conditions under which this matters
- Date: YYYY-MM-DD
- Ref: [first 60 chars of the Jira key + feature summary]
Return: file updated/created and summary of entry, OR 'no update required'."
Agent 3 — Instructions (general-purpose, model: <detection_model — §9 / §2.1 detection chain>):
"Post-write instructions review. Change summary:
[paste change summary block]
Check .github/copilot-instructions.md in the project root and ~/.copilot/copilot-instructions.md (global).
Determine if any doc-writing rules, guidance, or guardrails are missing because of what this run revealed (e.g., a repo-specific frontmatter field that must always be present, a cross-link pattern that's easy to miss, an image-policy rule that caught you out).
Skip if: the run followed existing conventions with no surprises. Only update if a concrete, recurring rule would have prevented a decision point or misunderstanding.
If YES: apply minimal, additive, scoped changes only — do not rewrite sections wholesale.
Return: what was changed and why, OR 'no update required'."
Agent 4 — Session maintenance (dev-workflows:impl-maintenance, model: <detection_model — §9 / §2.1 detection chain>):
"Analyse this session and return a Lessons Learned report.
Session handoff:
- Command run: document:
- What was done: [one-paragraph summary of the documentation produced]
- Key events: [BLOCK reviews encountered and their reason, ambiguous image policies, unresolved PRs, style-check failures, branch-naming conflicts — or 'none']
- Workarounds used: [manual steps not automated by the workflow — or 'none']
- Review verdict: [PASS | PASS WITH RECOMMENDATIONS | BLOCK]
- Test result: N/A (no tests in document:)
- Project root: [the resolved docs_repo_path (Phase 0)]"
Collect all four summaries for the Phase 9 report.
Persist plugin feedback (automatic). After Agent 4 (impl-maintenance)
returns, project its plugin-facing slice into the specs repo by citing
~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/feedback-emission.md and calling its
emit-auto entry point (§6). Pass Agent 4's Lessons Learned report,
command: document: (Jira mode), the run's jira_key and source, and
plugin_version (read from ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/.plugin/plugin.json).
emit-auto renders only the report's Command workflow improvements, New
agents / skills, and plugin Reference docs sections plus the Key
observations that triggered them (§4 plugin-facing predicate) — never
target-project copilot-instructions.md/hook advice — as origin: auto entries, dedupes by
stable id (§3), resolves the target via the §2 specs-first ladder, and writes
silently. List the persisted path (or "no plugin-facing signal — nothing
persisted") in the Phase 9 report's Session learnings line. ADDITIVE — the
impl-maintenance report still appears in the report; this step NEVER fails the
run, NEVER commits, and NEVER writes into the docs repo or the current working
directory.
Phase 8.5 — Finish & handoff
Run this phase only when Phase 6.3 wrote + committed in a git repo (write context docs_repo, or non_docs_repo confirmed at Phase 0) — i.e. a branch with this run's commits exists. Skip otherwise (nothing to hand off). Mechanics: ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/finish-and-handoff.md.
Step 1 — Squash (always)
Fold the run into clean history before handoff:
- Stage the run's uncommitted docs-repo edits — Phase 8 Agent 1 (doc index / cross-links) and Agent 3 (
copilot-instructions.md) may have edited without committing; the Phase 6.2 clean-tree check means everything uncommitted is this run's work.
- Compute the squash base: if Phase 6.2 recorded
profile_commit (inline-profiling run), base = profile_commit (keeps the profile-config commit as a distinct first commit → two commits); otherwise base = git merge-base <base_branch> HEAD (one commit).
git add the docs-repo changes → git reset --soft <squash-base> → one git commit. The message follows profile.commit_convention when present (dynatrace-docs: <JIRA-KEY> <summary>); for a repo with no such field, infer from recent git log / CONTRIBUTING, else fall back to <JIRA_KEY> <summary>. NEVER put the Jira key in a reader-visible changelog — the commit message carries traceability.
Step 2 — Offer push
choices: ["Push <branch> to origin now", "Skip — I'll push later", "Cancel"]
- Push →
git push -u origin <branch>; report the result. (git push is git-protocol, not a REST API — the zero-external-API invariant is preserved.)
- Skip → "Branch
<branch> ready with N commit(s). Push when ready."
- Cancel → stop and summarise.
Step 3 — Copy-paste PR draft (always; no API)
Per ~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/finish-and-handoff.md §4–§5:
- Detect the host from the docs repo's
git remote get-url origin (Bitbucket Cloud / Bitbucket Server / GitHub / other).
- Compose the draft: title (per
commit_convention); body — what was documented, the output files, the Phase 6.5 render-verification summary, deferred style/review/render items, a link to the Jira VI. When Phase 5.8 recorded any document-as-spec / skip-and-report decision, prepend a banner: > ⚠ DO NOT MERGE until <JIRA_KEY>-implementation-gaps.md is resolved.
- Write + show: write
<JIRA_KEY>-pr-draft.md to the vault project folder (find $VAULT_PATH/Projects -maxdepth 5 -type d -name "<JIRA_KEY>*"; ask if none) AND print it.
- Host footer: Bitbucket → "open a PR in the web UI and paste the title + body"; GitHub → additionally offer
gh pr create --title "<title>" --body-file <pr-draft path> that the user may run; other → "open a PR and paste the title + body". The plugin never opens the PR itself.
Carry the squash result, push outcome, and PR-draft path into the Phase 9 report.
Phase 9 — Final Report
Output a structured report — do NOT ask any closing confirmation:
## Jira-driven Documentation Report
### Classification
SIGNIFICANT — Jira-driven feature documentation has large blast radius if wrong
### Model Routing
- Session / writer model (current_model): [model] — [if it ran degraded: "Sonnet; user proceeded past the Phase 1.5 advisory" | "Sonnet; no Opus available" | "on §2 chain — no degradation"]
- doc-planner synthesis (planning_model): [model]
- Detection steps — jira-reader, diff-summarizer, doc-location-finder, counterpart-finder, docs-style-checker, doc-fixer, maintenance (detection_model): [model]
- doc-reviewer (review_model): [model]
- Opus available: [yes | no]
### Jira hierarchy summary
- VI: [<KEY>] [summary, 1 line]
- Linked items: [count by type — e.g. "3 Epics, 7 Stories, 2 Sub-tasks, 1 Research"]
- Themes: [2–4 bullet points from jira-reader]
### Repos analysed
- <repo-1> (<resolved repo_path>) — [N PRs in scope, M resolved, K unresolved]
- ...
### PRs in scope
- [PR URL] — status: [MERGED | OPEN | DECLINED | UNKNOWN], resolved_via: [pr_ref | branch_search | merge_commit | jira_key_commits | gh_cli | unresolved]
- ...
### Output file(s)
- [absolute path] — [kind: extend-existing | new-page-in-existing-section | new-section]
- ...
### Branch
[branch name created in Phase 6.2, e.g. docs/<jira-key>-<slug>] OR "N/A — no branch created (context: obsidian / plain_dir / user declined branching)"
### Render verification
- Build: [ran — pass/fail | no build command — boot is the proof | unverified (reason)]
- Smoke-check: [per space — passed (N pages, HTTP 200) | skipped (reason)] OR "not run (user skipped)"
- Cross-space invariant: [verified (markers present in target, absent in protected) | not checked | VIOLATION — see deferred items]
- Pages to visit: [the Phase 6.5 Step 3 table]
### Doc review verdict
[PASS | PASS WITH RECOMMENDATIONS | BLOCK] — [1-line summary of findings applied / deferred]
### Documentation (Agent 1)
- [file updated] — [what was added/changed] OR "no update required (reason)"
### Knowledge base (Agent 2)
- [file updated/created] — [summary of entry] OR "no update required"
### Instructions (Agent 3)
- [summary of change] OR "no update required"
### Session learnings (Agent 4)
- [top suggestions from impl-maintenance agent, or "no suggestions — routine session"]
### Screenshots to upload manually
[Only populated for the **Defer** path of Phase 6.1 — i.e. a target used image_policy: cdn_upload_required (or the user selected "Stage for manual upload" under the ambiguous branch) AND the user chose "Defer — stage with TODO placeholders" at the Phase 6.1 CDN handoff. For each staged screenshot: src (original user-provided path), staging path under <screenshot_staging_dir> (the persistent Obsidian project folder), the target page it belongs on, the proposed alt-text, and the upload_note from the planner. Omit this section entirely when no screenshots were staged — including when the user chose "Upload now" in Phase 6.1 (those images carry real CDN URLs in the markdown and need no manual step).]
### Implementation gaps (Jira vs source)
[Populated when Phase 5.8 produced any document-as-spec / skip-and-report decision. List each gap (claim, decision) and: "Bug-report draft written to <path>. If docs were branched, DO NOT merge the PR until these gaps are resolved." Omit when there were no discrepancies.]
### Skipped items
[Gaps the planner flagged with recommended_action: "skip with note in final report" — one line each; or "none"]
### Deferred items
[MINOR / NIT findings that were not applied, OR user-declined screenshots, OR doc-reviewer BLOCK findings that were overridden / deferred — one line each; or "none"]
### Assumptions & limitations
- [list any]
### Git state
[When Phase 8.5 ran: "Branch <name> — squashed to N commit(s); pushed to origin: <yes/no>; PR draft: <pr-draft path>." When Phase 8.5 was skipped (no branch/commits): "Working tree has uncommitted changes. document: (Jira mode) writes but does not commit in non-git contexts."]
### Next step
[Per `~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/next-phase-offer.md` — guidance only, never auto-invoked. Once **all** the VI's Epics are documented, draft/finalize the release note → `release-notes: <VI>` (VI-level; run once, not per Epic). If the review BLOCKED, resolve that first.]
### Context hygiene
Write the resume pointer at `<VI-dir>/dev-workflows/resume.md` (per `session-hygiene.md` §1). Then:
- **On to `release-notes: <VI>` (docs → PM handoff), even yourself?** → run **`/clear`** for a clean slate; the docs are on disk.
- Consider **`/rename <VI-ID>-<slug>-team`** to relocate this session later.
Guidance only — see `~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/session-hygiene.md`.
Phase 10 — Emit follow-up tasks
Terminal phase — runs AFTER the Phase 9 Final Report is composed; NEVER
interrupts an earlier phase. Persist the run's out-of-scope / manual-step
follow-ups as durable Obsidian tasks (and notes) by citing
~/.copilot/installed-plugins/ihudak-copilot-plugins/dev-workflows/skills/_shared/followup-emission.md and executing its steps
inline.
- Collect the follow-up items already aggregated in the Phase 9 report:
### Screenshots to upload manually, ### Implementation gaps (Jira vs source),
### Skipped items, and ### Deferred items.
- Filter them with the reference's §6 qualifying predicate — emit only