# Architecture Doc

> Produce or audit `docs/ARCHITECTURE_AND_DESIGN.md` for any codebase, standalone from the `/project` flow. Create mode reverse-engineers an architecture document from source for repos that lack one; Audit mode updates an existing document in place against the current code. Use when there is no architecture document and one is needed, when an existing architecture doc has drifted from the code, or when the user asks to "document this architecture", "reverse engineer the architecture", "create an architecture doc", or "audit the architecture doc against the current code". Operates on the current working directory by default; accepts an optional positional `target_path` argument.

- Skill: `ottawacloudconsulting/architecture-doc` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add ottawacloudconsulting/architecture-doc`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ottawacloudconsulting/architecture-doc/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: OttawaCloudConsulting (https://skillmd.com/u/ottawacloudconsulting)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/ottawacloudconsulting/architecture-doc

---


# /architecture-doc -- Standalone Architecture Documentation

Produces or maintains `docs/ARCHITECTURE_AND_DESIGN.md` for any codebase,
independent of the `/project → /define → /design` flow. Two modes:

- **Create** -- the document does not exist (or the user opted to overwrite).
  A scan sub-agent reads the code; a synthesis sub-agent writes a fresh
  document conforming to the canonical template at
  `skills/project/design/assets/architecture-template.md`.
- **Audit** -- the document already exists. The same scan runs, but the
  synthesis sub-agent preserves user-authored content by returning the full
  updated document inline for the orchestrator to write.

Both modes share the scan pipeline and diverge only in the synthesis
sub-agent. The skill opportunistically uses any MCP servers detected in the
session to enrich its scan and synthesis, and degrades gracefully when none
are present.

The authoritative design for this skill lives in
`docs/ARCHITECTURE_AND_DESIGN.md` **at the root of the `agentic-ai` repository
that hosts this skill** -- not the `docs/ARCHITECTURE_AND_DESIGN.md` this
skill produces at `target_path`. The two files share a filename but are
unrelated artifacts. Maintainers editing this orchestrator should read the
former.

## Rules

- **Read fresh every time.** Re-check the existing document on every
  invocation; never trust cached state from a prior run.
- **Mode is user-confirmed when ambiguous.** Existing-document detection must
  always go through `AskUserQuestion` before overwriting or editing an
  existing file. Absent files enter Create mode silently.
- **Path-bounded scan.** The scan sub-agent reads only paths inside the
  resolved `target_path`. Symlinks in `target_path` itself are tolerated;
  symlinks encountered during scan-time enumeration are never followed
  (Decision #9).
- **Never modify source code.** This skill is documentation-only. Output is
  restricted to `docs/ARCHITECTURE_AND_DESIGN.md` and the ephemeral scratch
  directory `docs/.architecture-doc/`.
- **Interactive prompts.** Use `AskUserQuestion` for all user-facing choices
  (max 2-4 options, ≤12-character headers).
- **No auto-dispatch.** Tell the user what to run next. Never auto-invoke
  another skill.

## Prerequisites

- A directory containing source code, supplied as `target_path` or defaulted
  to the current working directory.
- The canonical template at
  `skills/project/design/assets/architecture-template.md` and the canonical
  scan heuristics at
  `skills/project/design/references/gate-2-design.md` must be readable from
  the running session. The skill aborts with an explicit error if either is
  missing -- there is no vendored fallback (Decision #4, Decision #5).

## Step 1 -- Resolve and validate `target_path`

1. If the user supplied a positional argument to `/architecture-doc`, take
   that as `target_path`. Otherwise default to the current working directory.
2. Resolve `target_path` to its absolute, canonical form. Symlinks **in the
   path itself** are followed and tolerated (a user invoking the skill from a
   symlinked project root is supported -- Decision #9 relaxation, Data Flow
   step 2).
3. Confirm the resolved path:
   - exists, and
   - is a directory.
4. If either check fails, abort with a one-line error explaining which check
   failed and the resolved path. Do not create the scratch directory; do not
   proceed to Step 2.

The resolved absolute path is the working `target_path` for every later step
and is the prefix used by the scan sub-agent for path-boundary enforcement.

## Step 2 -- Existing-document detection and mode selection

This step runs **before** any other work -- before the MCP probe, before
scratch setup, before spawning any sub-agent.

1. Compute `doc_path = <target_path>/docs/ARCHITECTURE_AND_DESIGN.md`.
2. Test whether `doc_path` exists.
   - **Absent** → set `mode = Create` and proceed to Step 3. No prompt.
3. If present, attempt to read the file and apply the **plausibly Markdown**
   heuristic:
   - The file is readable as UTF-8 text, **and**
   - the file is non-empty, **and**
   - the first non-blank line begins with `#` (Markdown ATX heading).
4. Branch on the heuristic:
   - **Plausibly Markdown** → call `AskUserQuestion` with three options:
     - **Create** -- "Overwrite the existing document with a freshly produced one." (header: `Create`)
     - **Audit** -- "Update the existing document in place, preserving user-authored content." (header: `Audit`)
     - **Abort** -- "Stop without changing anything." (header: `Abort`)

     Set `mode` from the user's choice. On **Abort**, exit cleanly with a
     one-line message. On **Create**, the existing document will be
     overwritten by the synthesis sub-agent in a later step.
   - **Not plausibly Markdown** (unreadable, empty, or does not begin with a
     Markdown heading) → call `AskUserQuestion` with **only two** options:
     - **Overwrite** -- "Replace the existing file with a freshly produced architecture document." (header: `Overwrite`)
     - **Abort** -- "Stop without changing anything." (header: `Abort`)

     **Audit is not offered** in this branch because the file cannot be
     reliably parsed or extended. Set `mode = Create` if the user chose
     Overwrite; exit cleanly on Abort.
5. After Step 2 completes, `mode ∈ {Create, Audit}` and the orchestrator
   proceeds to Step 3.

Implementation notes:

- "Readable as UTF-8" is determined by attempting `Read` on the file. A
  failure or a binary-content indication counts as not plausibly Markdown.
- "First non-blank line" means the first line whose content, after stripping
  whitespace, is non-empty. Leading blank lines are ignored.
- **Known limitation -- YAML frontmatter is rejected.** A Markdown file that
  opens with a YAML frontmatter block (`---\ntitle: ...\n---\n\n# Heading`)
  fails the heuristic because its first non-blank line is `---`, not a
  Markdown ATX heading. Such files are routed to the **Not plausibly
  Markdown** branch and Audit is not offered. This is intentional: the
  canonical template at `skills/project/design/assets/architecture-template.md`
  does not use frontmatter, so the common case is unaffected. If a user needs
  Audit on a frontmatter-bearing doc, they can strip the frontmatter and
  re-run, or choose Overwrite.
- Mode selection is recorded only in the orchestrator's in-context state for
  this run. It is not written to disk in this step (`run.log` does not yet
  exist -- it is created in the scratch-setup step that belongs to a later
  feature).

## Step 3 -- MCP capability probe

This step runs after Step 2 (mode is set) and **before** Step 4 (scratch
setup). It does **not** write anything to disk because `run.log` has not yet
been created -- the deferred writes happen at the start of Step 4. Probe
results are held in orchestrator context throughout.

The probe discovers what enrichment tools are available in this session so
that Step 4.0 (language-server enrichment opt-in) and Step 8 (diagram
enrichment opt-in) can present accurate per-run opt-in prompts -- the
two halves of Feature 9. Per Decision #12, the probe is performed in
the orchestrator context with no probe sub-agent.

1. **Enumerate `mcp__`-prefixed tools.** Inspect the set of tools currently
   available to the orchestrator in this session. List every tool whose name
   begins with the prefix `mcp__`. Claude Code's MCP integration surfaces
   each MCP tool as a function named `mcp__<server>__<tool>`; the probe
   reads its own tool surface, not any external configuration file. The
   probe is intentionally **dynamic** -- it does not consult a hardcoded
   list of known servers, so new servers in the session are seen
   automatically and removed servers vanish from the result without code
   changes.
2. **If no `mcp__` tools are present**, set the in-context structure to
   `probe = { tools: [], categories: {} }`. The skill must continue to
   produce a full document with Read / Glob / Grep / Bash only; no warning
   is shown to the user, no degradation banner appears in the output.
   Proceed directly to Step 4.
3. **Categorise.** Read `references/mcp-probe.md` and apply its
   categorisation heuristics to each detected tool name. Each tool is
   assigned to **exactly one** category; the first matching category in the
   reference file's order wins; anything unmatched falls into `other`.
   Comparison is case-insensitive over the full `mcp__<server>__<tool>`
   name.
4. **Build the in-context probe structure** in the orchestrator:

   ```
   probe = {
     tools: ["mcp__mermaid-mcp__generate", ...],
     categories: {
       diagram:         ["mcp__mermaid-mcp__generate", ...],
       language_server: [...],
       code_search:     [...],
       repo_intel:      [...],
       other:           [...]
     }
   }
   ```

   Empty categories may be omitted from or kept in the structure -- both
   are acceptable. The categories that drive enrichment prompts in this
   iteration are `diagram` and `language_server`; the remaining categories
   are recorded for diagnostics only.
5. **Do not write to disk in this step.** The probe entries that belong in
   `run.log` are emitted by Step 4 immediately after the scratch directory
   and `run.log` are created. The exact `run.log` line format -- one entry
   per non-empty category plus a summary line, or a single
   `none-detected` summary line if `probe.tools` is empty -- is specified
   in `references/mcp-probe.md` under "Run-log lines emitted by Step 4".
6. Proceed to Step 4.

Implementation notes:

- Coupling risk (Decision #12): the probe relies on the
  `mcp__<server>__<tool>` naming convention. If that convention changes
  upstream, both the probe enumeration and the categorisation heuristics
  in `references/mcp-probe.md` break. This is the price of the
  cheapest-possible probe; the alternative -- a probe sub-agent making
  trial calls -- was rejected in Decision #12.
- The categories `code_search`, `repo_intel`, and `other` are recorded
  for diagnostics in `run.log` but **not** consumed by either enrichment
  opt-in -- only `diagram` (consumed by Step 8) and `language_server`
  (consumed by Step 4.0) drive enrichment prompts. This keeps the
  Feature 9 surface narrow without losing detection coverage; the
  unused categories remain available for future enrichments.

## Step 4 -- Scratch setup, run.log init, and scan sub-agent spawn

This step closes the Feature 3 deferred-write contract (writing the MCP
probe results to `run.log` once `run.log` exists), fires the
language-server enrichment opt-in (Feature 9, half of the Decision #17
opt-ins), spawns the scan sub-agent via the Agent tool, handles
preflight aborts and monorepo confirmation, runs a post-hoc
path-boundary check, and runs a secrets redaction pass over
`findings.md` before handing off to synthesis.

The scan sub-agent itself follows
`skills/architecture-doc/references/scan-agent-prompt.md`. The
orchestrator constructs the spawn prompt from that file and never
in-lines the scan logic itself.

### Step 4.0 -- Language-server enrichment opt-in (conditional)

This substep is the **scan-side half** of Feature 9. The diagram-side
half (Mermaid embedding) fires later in Step 9, after the review loop
has approved the textual content. Both opt-ins are governed by
Decision #17 (per-run opt-in) and use the canonical prompt copy in
`references/mcp-probe.md` (Per-run opt-in prompt text section).

The language-server opt-in MUST fire **here**, before Step 4.5
constructs the scan agent spawn prompt, because the opt-in result
gates whether language-server tool names are passed to the scan
agent via the `{{MCP_TOOLS_LIST}}` substitution marker.

1. Inspect `probe.categories.language_server` from Step 3.
2. If the list is empty (no language-server MCP detected), set
   `enrichment_choices.language_server = false` in orchestrator state
   and skip the rest of this substep -- proceed directly to Step 4.1.
   No prompt is shown to the user; this preserves the "baseline path
   unchanged when no enriching MCPs are present" guarantee from PRD
   Feature 8.
3. If the list is non-empty, read the canonical prompt copy from
   `references/mcp-probe.md` under the heading
   `### \`language_server\` enrichment prompt`. Use the question text
   and the two options (`Use LSP` / `Skip`) verbatim -- the
   reference file is the single source of truth for prompt wording so
   future copy edits land in one place.
4. Call `AskUserQuestion` with that question and those two options.
5. Branch on the user's choice:
   - **Use LSP**: set `enrichment_choices.language_server = true`.
     Hold the comma-separated list of detected language-server tool
     names in orchestrator state for Step 4.5's substitution.
   - **Skip**: set `enrichment_choices.language_server = false`. The
     scan agent will run with its baseline tool allowlist only.
6. The result is **not** written to `run.log` here -- `run.log` does
   not exist yet (Step 4.2 has not run). Hold the choice in
   orchestrator state and let Step 4.3 emit the deferred log line
   alongside the deferred probe writes. The deferred-write pattern is
   already established by the Feature 3 probe; reusing it keeps Step
   4 internally consistent.

Note that this substep does **not** depend on `<scratch_dir>` or
`run.log` existing. It runs cleanly even if Step 4.1 has not been
reached yet. The numbering (`4.0`) reflects "before everything else
in Step 4".

### Step 4.1 -- Create scratch directory

Create `<scratch_dir> = <target_path>/docs/.architecture-doc/`
(Decision #6).

- If `<target_path>/docs/` does not exist, create it (`mkdir -p`).
  Creating `docs/` here is harmless even in Audit mode -- the directory
  must exist for the existing document to have been detected in Step 2.
- If `<scratch_dir>` already exists from a prior aborted run, reuse
  it -- do not error. Cleanup at the end of Step 10 will remove it.
- If creation fails (e.g. `docs/` exists as a file, permissions denied),
  abort with the OS error and the resolved path. Do not proceed.

### Step 4.2 -- Initialise run.log

Create `<scratch_dir>/run.log` and write a session-start entry per
Decision #18 (`<ISO8601> <level> <phase> <message>` format):

```
<ISO8601> INFO session start target_path=<target_path> mode=<Create|Audit>
```

`<ISO8601>` is the current UTC time in `YYYY-MM-DDTHH:MM:SSZ` form.
`<Create|Audit>` is the mode resolved by Step 2.

### Step 4.3 -- Write deferred MCP probe results

Honour the Feature 3 contract from
`references/mcp-probe.md` ("Run-log lines emitted by Step 4"):

- For every non-empty category in the in-context `probe.categories`
  structure built by Step 3, append one line:
  ```
  <ISO8601> INFO probe <category>=<comma-separated tool names>
  ```
- Append one summary line listing the names of categories that had at
  least one detected tool:
  ```
  <ISO8601> INFO probe summary categories=<comma-separated category names>
  ```
- If `probe.tools` was empty, append a single line and skip the
  per-category lines:
  ```
  <ISO8601> INFO probe summary none-detected
  ```

These writes complete the Feature 3 deferred-write obligation. The
in-context `probe` structure is preserved in orchestrator state because
Step 8 (MCP-enriched synthesis) consumes `probe.categories.diagram` to
drive the diagram-enrichment opt-in.

Then append the **deferred Step 4.0 opt-in result** -- this completes
the Feature 9 language-server deferred-write obligation, the same
pattern Step 3 uses for the probe itself:

- If `probe.categories.language_server` was non-empty AND the user
  was prompted in Step 4.0, append:
  ```
  <ISO8601> INFO probe enrichment_optin language_server=<accepted|skipped>
  ```
  where `<accepted|skipped>` reflects the user's choice in Step 4.0.
- If `probe.categories.language_server` was empty (no prompt was
  shown), do not write any line for this enrichment. The
  `none-detected` summary line above already conveys the absence.

### Step 4.4 -- Read prompt template and canonical heuristics

Read both files:

1. `skills/architecture-doc/references/scan-agent-prompt.md` -- the
   scan sub-agent prompt template.
2. `skills/project/design/references/gate-2-design.md` -- the canonical
   scan heuristics referenced by the prompt (Decision #4 -- read in
   place, no vendored fallback).

If either read fails, abort with an explicit error naming the missing
file. The skill aborts with an explicit error if either canonical file
is missing -- there is no vendored fallback (Decisions #4 and #5).

### Step 4.5 -- Construct the scan agent spawn prompt

Take the contents of `scan-agent-prompt.md`, strip the preamble (the
"Substitution markers" section above the `--- BEGIN PROMPT ---`
marker), keep only the body between `--- BEGIN PROMPT ---` and
`--- END PROMPT ---`, and substitute these tokens:

| Marker | Value |
|--------|-------|
| `{{TARGET_PATH}}` | The absolute, canonical `target_path` resolved in Step 1. |
| `{{MCP_TOOLS_LIST}}` | Comma-separated names of the language-server MCP tools the user opted in to in Step 4.0 -- i.e. the contents of `probe.categories.language_server` IF `enrichment_choices.language_server == true`, otherwise the literal `none`. **Diagram MCPs and other categories are NEVER passed to the scan agent**, even if detected: scan-side enrichment is language-server-only by design (Decision #17, Feature 9 acceptance criteria). The diagram MCP is consumed later in Step 8, in the orchestrator context, after synthesis and before the review loop. |
| `{{MONOREPO_ACKNOWLEDGED}}` | The literal `false` on first spawn. On a re-spawn after the user confirmed the monorepo warning in Step 4.7, the literal `true`. |
| `{{SCRATCH_DIR}}` | `<target_path>/docs/.architecture-doc` (no trailing slash). |

After substitution, no marker may remain in the prompt body. Verify by
searching for the literal `{{` -- if any remain, abort with an
internal error.

### Step 4.6 -- Spawn the scan sub-agent

Spawn the scan sub-agent via the `Agent` tool:

- `subagent_type`: `general-purpose`. The Claude Code Agent tool does
  not expose per-spawn tool allowlists, so the scan agent's tool
  restrictions are enforced by **prompt-level discipline** -- the
  prompt forbids `Edit`, `Write` outside the scratch path, `Agent`,
  network tools, and any `Bash` command other than `ls -R` and
  `git log --oneline -20`. This matches the house pattern in
  `skills/project/design/`.
- `description`: `Architecture scan of <basename(target_path)>`.
- `prompt`: the constructed prompt body from Step 4.5.

Log the spawn to `run.log`:

```
<ISO8601> INFO scan spawn subagent_type=general-purpose monorepo_acknowledged=<true|false>
```

### Step 4.7 -- Handle agent return and write findings.md

The scan agent's return text begins with a `STATUS:` line. Parse the
first line of the return and branch:

- **`STATUS: success files_scanned=<N_arch>+<N_docs>`**
  - Parse the two integers separated by `+`. `<N_arch>` is the number
    of architecture files read by the scan agent in Steps 3-4 of its
    prompt (15-30 budget); `<N_docs>` is the number of doc files read
    by the harvest in Step 5 of its prompt (0-15 budget).
  - Log: `<ISO8601> INFO scan return status=success arch_files=<N_arch> doc_files=<N_docs>`.
  - **Extract findings content.** Parse the agent's return text for the
    literal markers `--- BEGIN FINDINGS ---` and `--- END FINDINGS ---`.
    Extract everything between these markers (excluding the markers
    themselves). If either marker is missing or malformed, treat this as
    an error (see error branch below).
  - **Write findings.md.** Use the `Write` tool to write the extracted
    content to `<scratch_dir>/findings.md`. Log:
    `<ISO8601> INFO scan findings_written path=<scratch_dir>/findings.md`.
  - Both counts are surfaced in the final session summary printed by
    Step 10 (cleanup) so the user knows how much architecture vs.
    documentation context fed the produced doc.
  - Proceed to Step 4.8 (path-boundary post-validation).

- **`STATUS: preflight_abort reason=<empty|binary_only>`**
  - Log: `<ISO8601> WARN scan return status=preflight_abort reason=<reason>`.
  - Print a one-line user-facing message:
    `Scan aborted: <reason>. No document was produced.`
  - Jump to Step 10 (cleanup) and exit.

- **`STATUS: monorepo_warning signal=<signal>`**
  - Log: `<ISO8601> WARN scan return status=monorepo_warning signal=<signal>`.
  - Call `AskUserQuestion` with two options:
    - **Continue** -- "Treat the monorepo root as a single project and scan it." (header: `Continue`)
    - **Abort** -- "Stop without producing a document." (header: `Abort`)
  - On **Abort**: log `<ISO8601> INFO scan monorepo abort`, jump to Step
    10 (cleanup), and exit.
  - On **Continue**: log `<ISO8601> INFO scan monorepo acknowledged`,
    re-spawn the scan agent by repeating Steps 4.5 through 4.7 with
    `{{MONOREPO_ACKNOWLEDGED}}` set to `true`. The re-spawned agent will
    skip the monorepo check and proceed.

- **`STATUS: error reason=<description>`** (or any unparseable return,
  or missing `--- BEGIN FINDINGS ---` / `--- END FINDINGS ---` markers)
  - Log: `<ISO8601> ERROR scan return status=error reason=<description>`.
  - Print: `Scan agent failed: <description>. No document was produced.`
  - Jump to Step 10 (cleanup) and exit.

### Step 4.8 -- Path-boundary post-validation

Read `<scratch_dir>/findings.md` (written by Step 4.7). Locate the
`## Files Cited` section
at the tail of the file (the scan agent's prompt requires every cited
file to appear there exactly once, one path per line, with no leading
bullet, backtick, or commentary). For each path in that section:

1. Resolve to its absolute, canonical form via `realpath` (using `Bash`,
   the one place the orchestrator uses `Bash` for filesystem operations
   outside the scan agent).
2. Verify the resolved absolute path begins with `<target_path>/`
   (with the trailing slash, to prevent prefix-matching escapes like
   `<target>foo/`).
3. If any cited path fails the check, log:
   ```
   <ISO8601> ERROR scan path_boundary_violation cited=<original> resolved=<absolute>
   ```
   then print `Path boundary violation in scan findings: <cited>`,
   jump to Step 10 (cleanup), and exit. Do **not** proceed to synthesis
   with a tainted findings file.

This is a defence-in-depth check on top of the prompt-level boundary
rule in `scan-agent-prompt.md`. Decision #9 makes path boundary a
security control; the cost of post-hoc validation is acceptable.

### Step 4.9 -- Secrets redaction pass

Read `skills/architecture-doc/references/redaction-patterns.md` for the
canonical pattern list and the application rules. For each pattern in
the listed order, apply a regex replace over `findings.md`, replacing
matches with the literal string `[REDACTED]`. Track the per-pattern hit
count.

Log the result to `run.log`:

```
<ISO8601> INFO scan redacted findings.md hits=<total>
```

If `<total>` is greater than zero, also log a list of the matched
pattern names so a post-mortem can identify what category of secret the
scan agent attempted to write:

```
<ISO8601> WARN scan redaction findings.md patterns=<name1,name2,...>
```

The pattern list, log-line format, application order, and known
limitations all live in `redaction-patterns.md` -- this step is the
caller, not the source of truth. Synthesis Steps 6 and 7 will run an
analogous redaction pass over the produced architecture document.

After Step 4.9 completes, control passes to Step 5.

## Step 5 -- Existing-documentation harvesting

Runs **inside the scan sub-agent**, not in the orchestrator. The
orchestrator does nothing extra for this step -- the harvest is
encoded in `references/scan-agent-prompt.md` under "Step 5 --
Existing documentation enumeration". Operationally, after the scan
agent finishes Step 4 (read selected source files) it enumerates
in-repo documentation -- `README*`, `ARCHITECTURE*`, `DESIGN*`,
`CONTRIBUTING*`, `CHANGELOG*`, `RUNBOOK*`, ADR directories,
`docs/**/*.md|.mdx|.rst`, and diagram source files (`.mmd`,
`.drawio`, `.puml`, `.plantuml`) -- caps the harvest at **15 doc
files** (a separate budget from the 15-30 architecture file scan in
Step 3), summarises each in 1-3 sentences, and writes the results to
`findings.md` under the `## Existing Documentation` section in a
parseable bullet format.

Synthesis (Steps 6 and 7) consumes the `## Existing Documentation`
section of `findings.md` and is expected to cite source doc files
(by relative path) when a Design Decision, Component, or Data Flow
entry is drawn from existing documentation rather than from source
code. The exact citation discipline lives in the synthesis prompt
template and the canonical architecture template; this step only
guarantees that the existing-doc inventory is present in
`findings.md` for synthesis to draw on.

Doc files cited in the harvest also appear in the `## Files Cited`
tail of `findings.md`, which means the orchestrator's Step 4.8
path-boundary post-validation already covers them -- no separate
boundary pass is needed. The Step 4.9 secrets redaction pass over
`findings.md` likewise applies to harvest output, so a doc file that
embeds an example credential is sanitised before synthesis sees it.

## Step 6 -- Synthesis sub-agent: Create mode

This step runs only when `mode = Create` (resolved by Step 2). When
`mode = Audit`, skip Step 6 entirely and proceed to Step 7.

Step 6 spawns a synthesis sub-agent that reads `findings.md` (the
Step 4 output, already redacted by Step 4.9) plus the canonical
template, and writes a fresh `docs/ARCHITECTURE_AND_DESIGN.md` from
scratch via a single `Write` call. The synthesis sub-agent prompt
template lives at
`skills/architecture-doc/references/synthesis-agent-prompt.md`; the
orchestrator selects the `BEGIN CREATE` / `END CREATE` block,
substitutes tokens, and passes the body to the Agent tool.

After the agent returns, the orchestrator runs the post-synthesis
secrets redaction pass (Decision #8) and a structural validation
pass (six required section headers must be present).

### Step 6.1 -- Verify the canonical template exists

Decision #5 makes the canonical template a hard runtime dependency
with no vendored fallback. Verify before paying any synthesis cost:

1. Resolve the absolute path to
   `skills/project/design/assets/architecture-template.md` relative
   to the running session's location of the `agentic-ai` repo
   hosting this skill.
2. `Read` the file. If the read fails for any reason, abort with
   the explicit error:
   `Canonical architecture template missing: <resolved path>. The architecture-doc skill requires skills/project/design/assets/architecture-template.md to be readable; there is no vendored fallback (Decision #5).`
3. Log to `run.log`:
   ```
   <ISO8601> INFO synthesis template_ok path=<resolved path>
   ```

The template's content is not held in orchestrator state -- the
synthesis sub-agent will Read it itself. The orchestrator only
verifies existence so the run fails fast rather than after the
scan.

### Step 6.2 -- Read the synthesis prompt template and select the CREATE block

1. `Read skills/architecture-doc/references/synthesis-agent-prompt.md`.
2. Locate the literal markers `## --- BEGIN CREATE ---` and
   `## --- END CREATE ---`. Keep only the body between them; discard
   everything before `## --- BEGIN CREATE ---` (the substitution-marker
   preamble) and everything from `## --- END CREATE ---` onward
   (which includes the Audit block stub).
3. If either marker is missing, abort with an internal error naming
   the missing marker.

### Step 6.3 -- Substitute tokens

Apply token replacement to the CREATE block body:

| Marker | Value |
|--------|-------|
| `{{TARGET_PATH}}` | The absolute, canonical `target_path` from Step 1. |
| `{{OUTPUT_DOC_PATH}}` | `<target_path>/docs/ARCHITECTURE_AND_DESIGN.md`. |
| `{{FINDINGS_PATH}}` | `<scratch_dir>/findings.md`. |
| `{{TEMPLATE_PATH}}` | The absolute path resolved in Step 6.1. |
| `{{SCRATCH_DIR}}` | `<target_path>/docs/.architecture-doc` (no trailing slash). |

After substitution, search for the literal `{{` in the prompt body.
If any markers remain, abort with an internal error -- the spawn
prompt is malformed.

### Step 6.4 -- Spawn the synthesis sub-agent

Spawn via the `Agent` tool:

- `subagent_type`: `general-purpose`. Same rationale as Step 4.6 --
  Claude Code's Agent tool does not expose per-spawn tool allowlists,
  so the synthesis agent's tool restrictions are enforced by
  prompt-level discipline (the HARD RULE blocks in
  `synthesis-agent-prompt.md`).
- `description`: `Architecture synthesis (Create) for <basename(target_path)>`.
- `prompt`: the substituted CREATE block body from Step 6.3.

Log the spawn to `run.log`:

```
<ISO8601> INFO synthesis spawn mode=Create subagent_type=general-purpose
```

### Step 6.5 -- Handle agent return and write output document

The synthesis agent's return text begins with a `STATUS:` line.
Parse the first line and branch:

- **`STATUS: success decisions=<N_dec> components=<N_comp>`**
  - Parse the two integers. `<N_dec>` is the number of Design
    Decision rows produced; `<N_comp>` is the number of Component
    Inventory rows.
  - Log: `<ISO8601> INFO synthesis return status=success decisions=<N_dec> components=<N_comp>`.
  - **Extract document content.** Parse the agent's return text for the
    literal markers `--- BEGIN DOCUMENT ---` and `--- END DOCUMENT ---`.
    Extract everything between these markers (excluding the markers
    themselves). If either marker is missing or malformed, treat this as
    an error (see error branch below).
  - **Write the output document.** Use the `Write` tool to write the
    extracted content to `<output_doc_path>`. Log:
    `<ISO8601> INFO synthesis document_written path=<output_doc_path>`.
  - Both counts are surfaced in the final session summary printed
    by Step 10.
  - Proceed to Step 6.6 (output verification).

- **`STATUS: error reason=<description>`** (or any unparseable return,
  or missing `--- BEGIN DOCUMENT ---` / `--- END DOCUMENT ---` markers)
  - Log: `<ISO8601> ERROR synthesis return status=error reason=<description>`.
  - Print: `Synthesis agent failed: <description>. No document was produced.`
  - Jump to Step 10 (cleanup) and exit. Do not run Step 6.6 / 6.7
    / 6.8.

### Step 6.6 -- Verify the output document is non-empty

Read `<output_doc_path>` (written by Step 6.5). If the file is empty
(zero bytes or whitespace only), log
`<ISO8601> ERROR synthesis output_empty path=<output_doc_path>`,
print `Synthesis produced an empty document.`, and jump to Step 10.

### Step 6.7 -- Secrets redaction pass over the output document

Read `skills/architecture-doc/references/redaction-patterns.md` for
the canonical pattern list and the application rules (Step 4.9 of
this orchestrator already uses the same file). For each pattern in
the listed order, apply a regex replace over the output document,
replacing matches with the literal string `[REDACTED]`. Track the
per-pattern hit count.

Log the result to `run.log`:

```
<ISO8601> INFO synthesis redacted ARCHITECTURE_AND_DESIGN.md hits=<total>
```

If `<total>` is greater than zero, also log the matched pattern names
(Decision #8 -- post-mortem visibility into what category of secret
the synthesis agent attempted to write):

```
<ISO8601> WARN synthesis redaction ARCHITECTURE_AND_DESIGN.md patterns=<name1,name2,...>
```

The post-synthesis pass is the **second** of the two redaction passes
specified by Decision #8 -- the first was Step 4.9 over `findings.md`.
This pass is defence-in-depth: the synthesis agent works from the
already-redacted findings file, so secrets should not normally appear
in the output, but the agent has Read access to source files for
citation verification (Step 6.3 in the prompt) and could in principle
re-introduce a secret it observed there.

### Step 6.8 -- Structural validation

PRD Feature 5 acceptance criterion: "Populates all six required
sections". After redaction, verify the output document contains all
six required ATX section headers, exactly as written below, in any
order:

```
## Design Decisions
## Component Inventory
## Data Flow
## File Organization
## Deployment & Operations
## Security Considerations
```

Each header must appear as a top-level `##` ATX heading on its own
line. If any are missing, log:

```
<ISO8601> WARN synthesis structural_validation missing=<comma-separated section names>
```

Do **not** abort the run on a structural validation failure -- the
review loop in Step 9 gives the user the opportunity to surface and
fix the gap. Surface the missing section names in the Step 9 summary
so the user can address them in the first review pass. If the
validation passes, log:

```
<ISO8601> INFO synthesis structural_validation ok
```

After Step 6.8 completes, control passes to Step 8 (the diagram
enrichment opt-in -- conditional on a diagram MCP being detected --
then onward to Step 9 review). Audit-mode Step 7 is skipped because
`mode = Create`.

## Step 7 -- Synthesis sub-agent: Audit mode

This step runs only when `mode = Audit` (resolved by Step 2). When
`mode = Create`, Step 6 ran instead and Step 7 is skipped entirely.

Step 7 spawns a synthesis sub-agent that reads `findings.md` (the
Step 4 output, already redacted by Step 4.9), reads the existing
`docs/ARCHITECTURE_AND_DESIGN.md` in full, and updates that file
**in place** via `Edit` calls. The agent never uses `Write`. The
substance of every Audit-mode rule lives in
`skills/architecture-doc/references/audit-mode.md`; the synthesis
prompt's AUDIT block is a thin caller. The orchestrator selects the
`BEGIN AUDIT` / `END AUDIT` block, substitutes tokens, and passes
the body to the Agent tool.

After the agent returns, the orchestrator runs the post-synthesis
secrets redaction pass (Decision #8 -- defence-in-depth) and a
soft-warn structural validation pass.

### Step 7.1 -- Verify the canonical template and audit rules exist

Decision #5 makes the canonical template a hard runtime dependency
with no vendored fallback. The Audit-mode rules file is the same
class of dependency for Audit mode. Verify both before paying any
synthesis cost:

1. Resolve the absolute path to
   `skills/project/design/assets/architecture-template.md` relative
   to the running session's location of the `agentic-ai` repo
   hosting this skill.
2. `Read` the template. If the read fails for any reason, abort
   with the explicit error:
   `Canonical architecture template missing: <resolved path>. The architecture-doc skill requires skills/project/design/assets/architecture-template.md to be readable; there is no vendored fallback (Decision #5).`
3. Resolve the absolute path to
   `skills/architecture-doc/references/audit-mode.md`.
4. `Read` the audit-mode rules. If the read fails for any reason,
   abort with the explicit error:
   `Audit-mode rules file missing: <resolved path>. Audit mode requires skills/architecture-doc/references/audit-mode.md to be readable.`
5. Log to `run.log`:
   ```
   <ISO8601> INFO synthesis template_ok path=<resolved template path>
   <ISO8601> INFO synthesis audit_rules_ok path=<resolved audit-mode.md path>
   ```

Neither file's contents are held in orchestrator state -- the
synthesis sub-agent will Read them itself. The orchestrator only
verifies existence so the run fails fast rather than after the
scan.

### Step 7.2 -- Read the synthesis prompt template and select the AUDIT block

1. `Read skills/architecture-doc/references/synthesis-agent-prompt.md`.
2. Locate the literal markers `## --- BEGIN AUDIT ---` and
   `## --- END AUDIT ---`. Keep only the body between them; discard
   everything before `## --- BEGIN AUDIT ---` (the substitution-marker
   preamble plus the entire CREATE block) and everything from
   `## --- END AUDIT ---` onward.
3. If either marker is missing, abort with an internal error naming
   the missing marker.

This is the mirror of Step 6.2, which selects the CREATE block.

### Step 7.3 -- Substitute tokens

Apply token replacement to the AUDIT block body:

| Marker | Value |
|--------|-------|
| `{{TARGET_PATH}}` | The absolute, canonical `target_path` from Step 1. |
| `{{OUTPUT_DOC_PATH}}` | `<target_path>/docs/ARCHITECTURE_AND_DESIGN.md` -- the existing doc (must already exist; mode = Audit was set in Step 2 because it does). |
| `{{FINDINGS_PATH}}` | `<scratch_dir>/findings.md`. |
| `{{TEMPLATE_PATH}}` | The absolute path resolved in Step 7.1. |
| `{{AUDIT_RULES_PATH}}` | The absolute path to `skills/architecture-doc/references/audit-mode.md` resolved in Step 7.1. |
| `{{SCRATCH_DIR}}` | `<target_path>/docs/.architecture-doc` (no trailing slash). |

After substitution, search for the literal `{{` in the prompt body.
If any markers remain, abort with an internal error -- the spawn
prompt is malformed.

### Step 7.4 -- Spawn the synthesis sub-agent

Spawn via the `Agent` tool:

- `subagent_type`: `general-purpose`. Same rationale as Step 6.4 --
  Claude Code's Agent tool does not expose per-spawn tool
  allowlists, so the synthesis agent's tool restrictions are
  enforced by prompt-level discipline (the HARD RULE blocks in
  the AUDIT block of `synthesis-agent-prompt.md` plus the rules in
  `audit-mode.md`).
- `description`: `Architecture synthesis (Audit) for <basename(target_path)>`.
- `prompt`: the substituted AUDIT block body from Step 7.3.

Log the spawn to `run.log`:

```
<ISO8601> INFO synthesis spawn mode=Audit subagent_type=general-purpose
```

### Step 7.5 -- Handle agent return and write output document

The synthesis agent's return text begins with a `STATUS:` line.
Parse the first line and branch:

- **`STATUS: success new_decisions=<N_new> findings=<N_findings> contradictions=<N_contra>`**
  - Parse the three integers. `<N_new>` is the number of new
    Design Decision rows appended to the existing table.
    `<N_findings>` is the total number of rows in the Audit
    Findings block (including `missing`-type rows for the
    appended decisions). `<N_contra>` is the subset of
    `<N_findings>` whose `Type` is `contradiction`. In the empty
    case all three are `0`.
  - Log: `<ISO8601> INFO synthesis return status=success mode=Audit new_decisions=<N_new> findings=<N_findings> contradictions=<N_contra>`.
  - **Extract document content.** Parse the agent's return text for the
    literal markers `--- BEGIN DOCUMENT ---` and `--- END DOCUMENT ---`.
    Extract everything between these markers (excluding the markers
    themselves). If either marker is missing or malformed, treat this as
    an error (see error branch below).
  - **Write the output document.** Use the `Write` tool to write the
    extracted content to `<output_doc_path>`. Log:
    `<ISO8601> INFO synthesis document_written path=<output_doc_path> mode=Audit`.
  - All three counts are surfaced in the final session summary
    printed by Step 10. The contradictions count is highlighted in
    the Step 9 review prompt because it is the most actionable
    signal for the user.
  - **Capture optional post-document summary text.** Per
    `references/audit-mode.md` ("After the document block, the agent
    MAY include a brief human-readable summary"), any text after
    the `--- END DOCUMENT ---` marker in the agent's return is held
    in orchestrator state as `audit_agent_summary` (capped at 200
    words; truncate silently if longer). Step 10.2 prints this
    verbatim under the fixed-shape summary block when it is
    non-empty. If the agent returned no text after the document
    block, leave `audit_agent_summary` unset.
  - Proceed to Step 7.6 (output verification).

- **`STATUS: error reason=<description>`** (or any unparseable return,
  or missing `--- BEGIN DOCUMENT ---` / `--- END DOCUMENT ---` markers)
  - Log: `<ISO8601> ERROR synthesis return status=error mode=Audit reason=<description>`.
  - Print: `Audit synthesis agent failed: <descri

…(truncated)
