smith-build · git:20260914.f9ed986 · 2026-09-14 · sha256 1cc56206fde5e3ad

smith-build git:20260914.f9ed986A

Immutable. This exact content is served forever at /api/v1/blob/1cc56206fde5e3ad.

---
name: smith-build
description: Autonomous build phase — generates tasks, implements, tests, commits, pushes, merges, and produces release notes. Runs without user interaction.
---

# SpecKit Autonomous Build

Executes the full build pipeline from answered questions through to merged PR and release notes. This command runs entirely without user interaction, using subagents to manage context.

**Arguments:** $ARGUMENTS

## Vault Logging

Throughout this action, log significant events to the vault session log. Read the session log path from `.smith/vault/.current-session`. If the file is missing or the vault is not initialized, skip all logging silently.

**Marker before first append**: the workflow-gate denies markerless Bash redirection, so `cat >> "$SESSION"` appends are blocked until the active-workflow marker exists. Create the marker (Phase 0 step 0 helper) FIRST, then write the invocation entry immediately after — do not log before the marker.

Append entries using this format:

```
### [HH:MM:SS] /smith-build <event>

**User Request:**
> <verbatim user message that triggered this action — if invoked via /smith-new, reference the original request logged there. If invoked manually for recovery, capture the recovery command.>

**Synthesized Input:** <brief summary of what's being built>
**Outcome:** <what happened>
**Artifacts:** <files created/modified>
**Systems affected:** <system IDs>
```

Log at these points:
1. **On invocation** — which feature is being built, fresh run or recovery, reference to original user request
2. **After each phase completes** — phase name, tasks completed count, key artifacts produced
3. **After system spec updates** — which system specs were updated and what changed
4. **After PR created** — PR number, title
5. **After merge** — success/failure, branch cleanup status
6. **On completion** — brief release notes summary, total files created/modified, services rebuilt

## Subagent Invocation Logging

Immediately before every Agent tool call in this workflow (including each phase subagent, testing subagent, and spec-update subagent), append a block to the session log. The Agent tool's return value does not expose `subagent_type` or `model` to the parent, so this is the only place that information can be captured.

```
### [HH:MM:SS] Subagent invoked: <description>

**Type:** <subagent_type or "general">
**Model:** <model override passed to Agent, or "inherited" if none>
```

After the Agent tool returns, the `subagent-vault-writeback.sh` hook automatically appends a matching "Subagent completed" block with metrics read from the sidechain transcript — do not duplicate that logging in the skill.

This command can be invoked in two ways:
1. **Automatically by `/smith-new`** after questions are answered (normal flow)
2. **Manually by the user** via `/smith-build` for recovery if a previous build failed partway

## Phase 0: Context Discovery

0. **Activate workflow tracking** — invoke the shipped helper to create the per-branch marker. The workflow-gate hook (PR #20) exempts this exact helper by basename so the bootstrap runs even when no marker exists yet (per spec/31-workflow-gate-bootstrap). The helper also stamps the current session log with a `workflow-start` line so `workflow-summary.sh --totals-only` can attribute tokens correctly:
   ```bash
   BRANCH=$(git rev-parse --abbrev-ref HEAD)
   # Derive a slug from the branch (drop number prefix if numbered):
   SLUG=$(echo "$BRANCH" | sed 's/^[0-9]*-//')
   ~/.smith/scripts/create-active-workflow.sh \
     --branch "$BRANCH" \
     --workflow smith-build \
     --slug "$SLUG" \
     --worktree "$(pwd)"
   ```
   (Falls back to `scripts/create-active-workflow.sh` in repo-dev layouts.) Clear this marker at the end of Phase 7 (after release notes) or on unrecoverable failure. Use the shipped helper so this works even on projects that deny `Bash(rm:*)`:
   ```bash
   .specify/scripts/bash/clear-active-workflow.sh "$BRANCH"
   ```

1. **Detect worktree context**:
   ```bash
   COMMON_DIR=$(git rev-parse --git-common-dir)
   GIT_DIR=$(git rev-parse --git-dir)
   ```
   - If `COMMON_DIR` ≠ `GIT_DIR`: we are in a worktree. Set `WORKTREE_MODE=true` and `WORKTREE_PATH=$(pwd)`.
   - Detect the primary repo path: `PRIMARY_REPO=$(git rev-parse --git-common-dir | sed 's|/\.git$||')`
   - Log worktree status to vault session log.

2. **Run prerequisites check**:
   ```bash
   .specify/scripts/bash/check-prerequisites.sh --json --paths-only
   ```
   Parse JSON for `FEATURE_DIR` and `AVAILABLE_DOCS`.

   If the script fails (e.g., not on a feature branch), check:
   - Is there a feature branch that matches `$ARGUMENTS`?
   - Are there incomplete tasks in any `specs/*/tasks.md`?
   - If recovery is possible, switch to the correct branch and retry.
   - If not, ERROR with guidance.

3. **Load feature context** from FEATURE_DIR:
   - `spec.md` (REQUIRED)
   - `plan.md` (REQUIRED)
   - `questions.md` (REQUIRED — verify Status is "ANSWERED")
   - `tasks.md` (OPTIONAL — may not exist yet if this is first run)
   - `data-model.md` (IF EXISTS)
   - `contracts/` (IF EXISTS)
   - `research.md` (IF EXISTS)
   - `quickstart.md` (IF EXISTS)

## Ledger Context (Optional)

If `.smith/vault/ledger/` exists and contains non-empty files, load relevant Ledger sections to inform this workflow. If the directory is missing, empty, or unreadable, skip silently — the Ledger is purely additive and never required.

1. Check: `ls .smith/vault/ledger/*.md 2>/dev/null`
2. If files exist, read the following sections (higher-confidence entries first, truncate at ~2000 tokens per file):
   - `.smith/vault/ledger/patterns.md`
   - `.smith/vault/ledger/antipatterns.md`
   - `.smith/vault/ledger/tool-preferences.md`
   - `.smith/vault/ledger/edge-cases.md`
   - `.smith/vault/ledger/project-quirks.md`
3. Use loaded patterns as additional context — not as hard rules. The Ledger informs judgment, it does not override spec/plan/constitution.
4. **Budget violation tracking**: If any Ledger file was truncated (entries were dropped to fit within the ~2000 token budget per file), increment `context_budget_violations` in `.smith/vault/ledger/.meta.json` by 1. If `.meta.json` does not exist, create it from the default template first. This signal tells the reconciliation system that the Ledger is too large for the configured budget.

4. **Determine build state** (for recovery):
   - If `tasks.md` exists, check for completed tasks `[X]` vs incomplete `[ ]`
   - If some tasks are complete, this is a **recovery run** — skip to Phase 2 (implementation)
   - If no tasks.md exists, this is a **fresh run** — start from Phase 1

## Phase 1: Task Generation (Subagent)

Launch a subagent to generate the task breakdown.

The subagent should:

1. **Read artifacts**: spec.md, plan.md, data-model.md, contracts/, research.md, quickstart.md
2. **Generate `tasks.md`** following the strict format:
   ```
   - [ ] [TaskID] [P?] [Story?] Description with file path
   ```
   - Phase 1: Setup (project initialization)
   - Phase 2: Foundational (blocking prerequisites)
   - Phase 3+: User Stories in priority order
   - Final Phase: Polish & Cross-Cutting Concerns
3. **Run consistency analysis** (`smith-analyze` logic):
   - Check spec ↔ plan ↔ tasks alignment
   - Check for missing coverage, contradictions
   - If CRITICAL issues found: fix them in-place (do not halt)
   - Log any issues found for the release notes

## Ledger-Informed Auto-Retry

If the build execution fails, check config for auto-retry:

1. Read `.smith/config.json` — check `ledger.auto_retry` and `ledger.max_retries`
2. If `auto_retry` is `false` (default) or config is missing, do NOT retry — fail normally
3. If `auto_retry` is `true`:
   a. Re-read `.smith/vault/ledger/antipatterns.md` to get the latest failure patterns
   b. Analyze the failure against known antipatterns to adjust the approach
   c. Retry the execution with the adjusted approach
   d. Repeat up to `max_retries` times (default: 2), re-reading antipatterns before each attempt
   e. If all retries exhausted, fail with a summary of all attempts
4. Each retry attempt is logged to the session log with attempt number and adjusted approach

Note: Auto-retry applies to the Phase 2 implementation loop. If a phase's subagent fails after 3 internal attempts AND auto-retry is enabled, the entire phase is retried with updated Ledger context.

## Phase 2: Implementation (Subagent per Phase)

Execute tasks phase-by-phase, each phase in its own subagent to manage context.

### Pre-implementation checks:

1. **Verify/create ignore files** based on plan.md tech stack:
   - `.gitignore`, `.dockerignore`, `.eslintignore`, `.prettierignore` as applicable
   - Only append missing patterns to existing files

2. **Parse tasks.md** to extract phases and their tasks.

### Execute each phase:

For each phase in tasks.md:

1. **Launch a subagent** with:
   - The phase's tasks (incomplete ones only)
   - Relevant context: plan.md tech stack, data-model.md, contracts/
   - File paths from task descriptions
   - Instructions to mark each task `[X]` in tasks.md upon completion

2. **Phase execution rules**:
   - Sequential tasks: execute in order
   - Parallel tasks [P]: can run together (but subagent decides based on file conflicts)
   - If a task fails: attempt fix up to 3 times, then log error and continue with remaining tasks
   - After each task completion, update tasks.md with `[X]` marker

3. **Phase completion check**:
   - Verify all tasks in the phase are marked `[X]`
   - If any failed permanently, log them for the summary
   - Proceed to next phase

### Implementation rules:
- Follow the plan.md architecture and file structure
- Respect data-model.md entity definitions
- Match contracts/ API specifications
- Use existing project patterns (read surrounding code before writing)
- Follow constitution.md principles
- **Clean-code architecture** — apply this checklist directly (it implements
  the constitution's **Clean Architecture Policy** and mirrors the `/smith-clean-code`
  skill; do NOT rely on being able to load that skill, as build subagents may
  not have skill access):
  - Small, single-responsibility functions and files; one clear reason to change.
  - Intention-revealing names (avoid `data`, `temp`, `handler`, `util` when a
    meaningful name exists).
  - Guard clauses over deep nesting; keep control flow shallow.
  - Separation of concerns — keep I/O, business rules, and persistence in
    distinct units.
  - No dead code, commented-out blocks, or duplicated logic.
  - Honor the file structure `plan.md` prescribed; do not collapse it back into
    large monolithic files.
- **Reuse over duplication**: before writing new code, check for an existing
  component that already does it — extend or import it rather than recreating
  it. Use the reuse list in `plan.md` (and `/smith-navigate` / `.smith/index/`
  when available) to locate existing modules, services, and utilities. Do not
  copy-paste near-identical logic; factor shared logic into a common helper.
- **Keep files small**: follow the constitution **File Size Policy** (300-line
  soft target, 500-line decomposition threshold). When a file you are editing
  approaches the threshold, split it proactively during the build rather than
  leaving it for the post-hoc File Size Warnings flag in the PR body (§5.3).
- **After any code changes to a Docker service**: run `docker compose up -d --build <service>` immediately

## Phase 3: Testing (Subagent)

Launch a testing subagent after all implementation is complete.

### 3.1 Unit Tests
- **If frontend code changed**: `cd services/command-center && pnpm test`
- **If Python service changed**: `cd services/<service> && poetry run pytest`
- Run existing test suites — do NOT skip tests

### 3.2 Playwright E2E Tests (MANDATORY for UI changes)
- **Check if any frontend files were modified** in this feature:
  - Files matching `services/command-center/src/components/**`
  - Files matching `services/command-center/src/pages/**`
  - Files matching `services/command-center/src/hooks/**`
  - Files matching `services/command-center/src/App.tsx`
- **If YES**:
  1. Run existing Playwright suite for regression: `cd services/command-center && pnpm exec playwright test`
  2. Write NEW Playwright tests for the changed/added UI flows
  3. Run the new tests
- **If NO frontend changes**: Skip Playwright

### 3.3 Test Failure Handling
- If tests fail: fix the code and re-run (up to 3 attempts per failure)
- If a test is flaky (passes on retry without code changes): note in release notes
- If tests cannot be fixed after 3 attempts: log the failure and continue
  - The release notes will flag this as requiring manual attention

## Phase 3.5: Clean Code Review Pass

Launch exactly ONE subagent (Task tool) to evaluate the full branch diff vs
`$BASE_BRANCH` against `smith-clean-code`'s rubric, strictly after Phase 3
has reached a passing state and strictly before Phase 4 begins. This is an
ordering precondition only — flag-never-block still governs the PR outcome
(§5.4), never this pass's own invocation.

**Invocation.** Thread the same `WORKTREE_PATH`/`BASE_BRANCH` context
Phase 4/Phase 5 already use:
```bash
BASE_BRANCH=$(.specify/scripts/bash/get-base-branch.sh)
```
Diff against `$BASE_BRANCH` only — never a hardcoded or inferred ref. Pin
the subagent to `model: sonnet` (not Haiku — this pass makes
auto-fix judgment calls, not narrow classification/lookup).

**Rubric delivery.** The subagent Reads `skills/smith-clean-code/SKILL.md`
(worktree copy first), falling back to `~/.claude/skills/smith-clean-code/SKILL.md`
(installed copy) only when the worktree copy is unavailable. It locates the
`## Review Process`, `## Decision Rules`, and `## What You Should Avoid`
sections by HEADING — e.g. `grep -n '^## Review Process'`, then Read from
that line to the next `^## ` heading — never by hardcoded line number,
since that file is edited independently of this feature and any cited
range would drift out of date.

**Findings contract.** Each finding carries exactly one severity
(Critical/High/Medium/Low) plus: Location (`path:line`), Tenet violated
(short label — e.g. "Deep nesting", "Duplicated logic", "God
function/file", "Unclear naming", "Mixed responsibilities"),
Behavior-preserving fix available (true/false), Fix-safety
(clear/unclear), Auto-fix eligible (derived, see below), Fix applied
(true/false — set only once the edit is actually made), and a 1-2
sentence Rationale. A finding missing any field defaults to Fix-safety:
unclear (flag, never fix).

**Auto-fix eligibility.** A finding is auto-fix eligible if and only if
it is behavior-preserving AND its fix-safety is clear. Not
behavior-preserving, or fix-safety unclear → always FLAG, never auto-fix.
State explicitly: a Critical finding whose only available fix would
change program behavior is NEVER auto-fixed, always flagged, regardless
of configuration — no override may relax this.

**`.meta` coverage.** This pass does NOT add its own proactive
`.meta`-write step. For builds launched via `smith-new`, that workflow's
existing per-edit `.meta` instruction already covers every edit the build
subagent makes, auto-fix edits included. For standalone `smith-build`
runs, auto-fix edits fall back to the existing passive §5.3.1 Description
Coverage Warnings scan, exactly like ordinary Phase 2 implementation
edits already do.

**No `tasks.md` coupling.** Auto-fix edits from this pass are polish, not
tracked tasks — they require no corresponding `tasks.md` change.

**Bounded re-test.** If one or more auto-fixes were applied (any count),
run exactly one full re-run of Phase 3 in its entirety (3.1 → 3.2 → 3.3).
If zero auto-fixes were applied, Phase 3 MUST NOT be re-run. A failing
re-run resolves entirely via Phase 3.3's own existing bounded-attempts
behavior — this pass adds no second retry loop, no second fix batch, and
does NOT re-review the diff, regardless of the re-run's outcome. The
bound is exactly: one review → at most one fix-application batch → at
most one Phase 3 re-run. No step repeats.

**Unresolved findings → scratch file.** Findings where `Fix applied:
false` are written by the review subagent itself (its own output — there
is no deterministic scan block to write here, unlike §5.3/§5.3.1, since
judging "is this a god function" is not something a script can do) to
`/tmp/smith-build-clean-code-findings.txt`:
```
- **[<Severity>]** `<path>:<line>` — <one-line description> (tenet: <Tenet violated>)
```
Medium, High, and Critical findings each get one line. Low-severity
findings are never listed individually — if any remain unfixed, append
exactly one trailing `+ N low-severity notes` line instead (omitted when
N=0). An auto-fixed finding contributes nothing to this file.

## Phase 3.6: Security Review Pass

Runs exactly once per build, strictly after Phase 3.5 completes (an ordering
precondition only, independent of its outcome) and strictly before Phase 4 begins
(FR-2). Never re-entered.

**Invocation.**
```bash
BASE_BRANCH=$(.specify/scripts/bash/get-base-branch.sh)
```
Diff against `$BASE_BRANCH` only.

**Step 1 — presence-detect.** Resolve `detect-scanners.sh` (installed-path-preferred,
repo-dev fallback — the convention `scripts/install.sh` stages this script family
under) and run it once, before any layer:
```bash
for cand in "$HOME/.smith/scripts/security/detect-scanners.sh" scripts/security/detect-scanners.sh; do
  [ -f "$cand" ] && DETECT_SCANNERS="$cand" && break
done
SCANNERS=$(bash "$DETECT_SCANNERS")   # gitleaks/semgrep/bandit=present|absent, one line each
```
Write `/tmp/smith-build-security-layers-ran.txt` (this feature's `data-model.md` §4)
unconditionally, derived from `$SCANNERS`: `layer1_builtin=ran`,
`layer1_gitleaks=ran|skipped_absent`, `layer2_sast=ran:<tool-name>|skipped_absent`,
`layer3_llm=ran`.

**Step 2 — Layer 1, unconditional (FR-8).** Resolve `secret-scan.sh` the same way and
invoke it, adding `--with-gitleaks` only if `gitleaks=present`:
```bash
for cand in "$HOME/.smith/scripts/security/secret-scan.sh" scripts/security/secret-scan.sh; do
  [ -f "$cand" ] && SECRET_SCAN="$cand" && break
done
echo "$SCANNERS" | grep -q '^gitleaks=present$' && GL="--with-gitleaks" || GL=""
bash "$SECRET_SCAN" --diff-base "$BASE_BRANCH" $GL
```
Exit `0` clean, `1` findings on stdout (parse, not a failure), `2` internal error
(log, treat this layer as skipped — not fatal). This is the only guaranteed-coverage
deterministic layer; must work correctly with zero external scanners installed
(FR-8).

**Step 3 — Layer 2, conditional.** If Step 1 reported `semgrep=present` and/or
`bandit=present`, run each against the same `git diff "$BASE_BRANCH" --name-only`
file list, normalized into this feature's `data-model.md` §2 five-field format
(`pattern-id` = `semgrep:<rule-id>` / `bandit:<check-id>`). Silently skip entirely if
neither is present (FR-9) — no error, no install attempt; neither tool is ever added
as an installed dependency of Smith or the project.

**Step 4 — Layer 3, unconditional.** Launch exactly ONE subagent (Task tool) to
review the full `git diff "$BASE_BRANCH"` against this rubric, stated verbatim:
injection (SQL/command/template), authentication/authorization flaws,
secrets/credential handling, unsafe deserialization or `eval`-family use, path
traversal, SSRF and unvalidated redirects, cryptographic misuse, sensitive-data
logging or exposure, dependency-adjacent code smells (not CVE/SCA scanning), and race
conditions/TOCTOU in security-relevant paths. Do NOT invoke Smith's built-in
`/security-review` capability — this rubric is the sole methodology. Pin `model:
opus`; read `.smith/config.json`'s `security_review.review_model`
(haiku|sonnet|opus|fable) to override, using the same file-exists-and-parses validity
gate as every other config read in this pipeline — missing/malformed config or key
defaults to `opus`.

**Findings contract.** Every finding carries exactly one Severity
(Critical/High/Medium/Low), `path:line` Location, Category, a 1-2 sentence Rationale,
and the originating Layer (1/2/3) — this feature's `data-model.md` §3.

**No auto-fix, ever (FR-13).** This phase makes ZERO Write/Edit calls to the working
tree, for any finding, any layer, any configuration — unlike Phase 3.5, there is no
eligibility test, because none exists.

**Step 5 — merge + decide.** Merge all layers' findings; evaluate this feature's
`data-model.md` §5 tier × severity × layer decision table against
`.smith/config.json`'s `security_review.enforcement_tier` (default `flag` if
absent/malformed, same defensive read as `review_model` above). One row applies in
every build regardless of tier: **any Critical Layer 1 (secret) finding ALWAYS
terminates** — non-bypassable, independent of `enforcement_tier`. The per-line
`# smith-secret-scan: allow` marker (applied before the scan runs) is the sole
false-positive remedy — no runtime-confirmation escape hatch, unlike the
browser-production confirm-gate (this is the system's second non-bypassable denial).

**Terminate branch.** Other layers may still finish evaluating so the eventual record
lists everything found, but nothing from this run reaches the flag-only scratch file
— the outcome is terminated, not a partial flag+terminate mix (NFR-6). Phase 4 never
begins, so Phase 5.1 (Commit)/5.2 (Push) never execute. Write a hard-stop marker to
the vault session log using this file's own `### [HH:MM:SS] /smith-build <event>`
format, `**Outcome:**` naming the terminating finding(s) (severity, `path:line`,
category, layer — excerpt REDACTED per this feature's `data-model.md` §2, no
internal-only exception), plus an explicit `**Hard-stop:** Security Review Pass
(Phase 3.6) terminated this build before Phase 4.` line. Surface the stop via Phase
7.5's Display Summary mechanism ("build terminated at Phase 3.6" instead of a PR
link). Preserve the worktree exactly like Phase 7.3's "on failure" convention; leave
the active-workflow marker uncleared. NEVER a prompt — this is a log entry, not a
pause (NFR-1).

**Flag branch.** If nothing resolves to "terminate," write every non-terminating
finding to `/tmp/smith-build-security-findings.txt` per this feature's
`data-model.md` §4 line format (Medium/High/Critical listed individually, Low folded
into one trailing `+ N low-severity notes` line, omitted when N=0) and proceed to
Phase 4 exactly like Phase 3.5 does today.

## Phase 4: Spec Updates (Subagent)

Launch a subagent to update related system spec files.

1. **Identify modified files** from git diff against the configured base branch:
   ```bash
   BASE_BRANCH=$(.specify/scripts/bash/get-base-branch.sh)
   git diff "$BASE_BRANCH" --name-only
   ```

2. **Map modified files to system specs**:
   - `services/command-center/` → `specs/system-15-command-center/spec.md`
   - `services/email-pipeline/` → `specs/system-03-email-archive-contact-graph/spec.md`
   - `services/sentiment-engine/` → `specs/sentiment-engine/spec.md`
   - `services/communication-triage/` → `specs/system-05-communication-triage/spec.md`
   - `services/voice-training/` → `specs/system-04-personal-voice/spec.md`
   - `docker-compose.yml` → `specs/system-01-core-infrastructure/spec.md`
   - Other mappings as discovered from `specs/*/spec.md` content

3. **For each affected spec.md**:
   - Read the current spec
   - Add an "Implementation History" section (or append to existing)
   - Add a dated entry describing changes relevant to that system
   - Keep entries concise and factual

4. **Update STATUS.md** at project root with current progress.

### 4.5 System Spec Updates via `.specify/systems/`

After updating the legacy `specs/system-*/spec.md` files above, also update the canonical system specs in `.specify/systems/`:

1. **Read the feature spec frontmatter** — extract `primary_system` and `also_affects` fields. If the feature spec has no frontmatter (legacy spec in `specs/`), fall back to the file-path mapping in step 2 above.

2. **Update primary system spec** — Read `.specify/systems/<primary-system>/spec.md` and update any sections affected by the feature:
   - New API endpoints or modified routes
   - New or changed data models / database tables
   - Changed behavior or configuration
   - New dependencies or service interactions

3. **Update affected system specs** — For each system in `also_affects`, read its `.specify/systems/<system>/spec.md` and update relevant sections.

4. **Log updates to vault** — If `.smith/vault/.current-session` exists, append an entry to the session log noting which system specs were updated and what changed.

5. **Commit system spec updates** as part of the same feature branch before creating the PR.

If the build cannot determine what to update in a system spec (ambiguous changes), flag this in the vault session log for the user to review rather than making incorrect updates.

## Phase 5: Commit, Push & Merge

### 5.1 Commit
```bash
git add <all modified files — list explicitly, not git add -A>
git commit -m "<conventional commit message>"
```

- Use conventional commits format
- Reference the feature spec in the commit message
- Stage files explicitly (never `git add -A` or `git add .`)
- Do NOT stage `.env` files or credentials

### 5.2 Push
```bash
git push -u origin <branch-name>
```

### 5.3 Pre-PR File-Size Scan

Before composing the PR body, scan all files modified on this branch for
oversized source files. This is a non-blocking advisory — always proceed
with the PR.

By this point, Phase 3.5 (Clean Code Review Pass) has already run and any
auto-fixes it applied are already in the working tree, so this scan's (and
§5.3.1's) `git diff $BASE_BRANCH` snapshot naturally reflects the post-fix
diff — no separate re-scan or staleness-avoidance step exists or is needed.

```bash
# Enumerate files changed vs the configured base branch
BASE_BRANCH=$(.specify/scripts/bash/get-base-branch.sh)
git diff "$BASE_BRANCH" --name-only > /tmp/smith-build-changed.txt

# For each modified file that exists on disk, count lines
while IFS= read -r f; do
  [ -f "$f" ] || continue
  lines=$(wc -l < "$f" | tr -d ' ')
  if [ "$lines" -gt 300 ]; then
    printf -- "- \`%s\` — %s lines (exceeds 300)\n" "$f" "$lines"
  fi
done < /tmp/smith-build-changed.txt > /tmp/smith-build-oversized.txt
```

If `/tmp/smith-build-oversized.txt` is non-empty, include a **"File Size
Warnings"** section in the PR body (see Step 5.4 template). If empty, omit
the section entirely.

Source extensions in scope: `.py`, `.js`, `.jsx`, `.ts`, `.tsx`, `.css`,
`.html`, `.sh`. Exclude paths matching `vendor/`, `node_modules/`, `.venv/`,
`dist/`, `build/`, `.smith/`.

This is a FLAG, never a blocker. Always proceed with PR creation.

### 5.3.1 Pre-PR Description Coverage Scan

In addition to the file-size flag, scan the diff for methods that were
ADDED or EDITED in this PR but lack a `.meta` description. This is the
v2 description-coverage check from data-model.md §9. Like the file-size
flag, it is informational — the PR opens unconditionally.

```bash
# Reuse /tmp/smith-build-changed.txt from Step 5.3 above.
> /tmp/smith-build-coverage-misses.txt

while IFS= read -r f; do
  case "$f" in
    *.py|*.js|*.jsx|*.ts|*.tsx) ;;
    *) continue ;;
  esac
  [ -f "$f" ] || continue

  # Skip files inside excluded directories.
  case "$f" in
    vendor/*|*/vendor/*|node_modules/*|*/node_modules/*|.venv/*|*/.venv/*|dist/*|*/dist/*|build/*|*/build/*|.smith/*|*/.smith/*) continue ;;
  esac

  # Resolve parser path: prefer per-project override, then ~/.smith,
  # then repo-shipped parsers.
  case "$f" in
    *.py)
      for cand in .smith/scripts/parse-python.py "$HOME/.smith/scripts/parse-python.py" scripts/parsers/parse-python.py; do
        [ -f "$cand" ] && PARSER="python3 $cand" && break
      done ;;
    *)
      for cand in .smith/scripts/parse-js.js "$HOME/.smith/scripts/parse-js.js" scripts/parsers/parse-js.js; do
        [ -f "$cand" ] && PARSER="node $cand" && break
      done ;;
  esac
  [ -z "${PARSER:-}" ] && continue

  # Parse the file at HEAD (current branch). Capture the (id, name, scope)
  # triples plus class scope.
  CUR_JSON=$($PARSER "$f" 2>/dev/null || true)
  [ -z "$CUR_JSON" ] && continue

  # Build a list of HEAD method ids and their qualified names.
  python3 - "$f" "$CUR_JSON" >> /tmp/smith-build-coverage-misses.txt <<'PY' || true
import json, os, sys, subprocess, re

rel = sys.argv[1]
cur = json.loads(sys.argv[2])

# Collect (id, qualified_name) for HEAD.
head_methods = []
for fn in cur.get("functions") or []:
    fid = fn.get("id")
    name = fn.get("name", "")
    if fid:
        head_methods.append((fid, f"{rel}::{name}"))
for cls in cur.get("classes") or []:
    cname = cls.get("name", "")
    for m in cls.get("methods") or []:
        mid = m.get("id")
        mname = m.get("name", "")
        if mid:
            head_methods.append((mid, f"{rel}::{cname}::{mname}"))

# Compare against `git show main:<file>` parse to find added/changed ids.
try:
    main_src = subprocess.check_output(
        ["git", "show", f"main:{rel}"], stderr=subprocess.DEVNULL
    ).decode("utf-8", errors="replace")
except subprocess.CalledProcessError:
    main_src = None

prev_ids = set()
if main_src is not None:
    # Re-parse main:<file>. The stable method id incorporates the
    # project-relative module_path, so we MUST stage the bytes at the
    # same relative path inside a scratch directory and run the parser
    # with that directory as CWD. Otherwise the temp-file path leaks
    # into the id hash and every method looks "added".
    import tempfile, pathlib
    suffix = pathlib.Path(rel).suffix
    parser_cmd = os.environ.get("SMITH_PARSER_CMD", "")
    if not parser_cmd:
        if suffix == ".py":
            for cand in (".smith/scripts/parse-python.py",
                         os.path.expanduser("~/.smith/scripts/parse-python.py"),
                         "scripts/parsers/parse-python.py"):
                if os.path.isfile(cand):
                    parser_cmd = f"python3 {os.path.abspath(cand)}"
                    break
        else:
            for cand in (".smith/scripts/parse-js.js",
                         os.path.expanduser("~/.smith/scripts/parse-js.js"),
                         "scripts/parsers/parse-js.js"):
                if os.path.isfile(cand):
                    parser_cmd = f"node {os.path.abspath(cand)}"
                    break
    if parser_cmd:
        with tempfile.TemporaryDirectory() as scratch:
            staged = os.path.join(scratch, rel)
            os.makedirs(os.path.dirname(staged), exist_ok=True)
            with open(staged, "w", encoding="utf-8") as fh:
                fh.write(main_src)
            try:
                out = subprocess.check_output(
                    parser_cmd.split() + [rel],
                    stderr=subprocess.DEVNULL,
                    cwd=scratch,
                )
                prev = json.loads(out.decode("utf-8", errors="replace"))
                for fn in prev.get("functions") or []:
                    if fn.get("id"):
                        prev_ids.add(fn["id"])
                for cls in prev.get("classes") or []:
                    for m in cls.get("methods") or []:
                        if m.get("id"):
                            prev_ids.add(m["id"])
            except (subprocess.CalledProcessError, json.JSONDecodeError):
                pass

# Touched = HEAD ids not in main (added) OR signature changed (id differs).
touched = [(fid, qname) for (fid, qname) in head_methods if fid not in prev_ids]

# Load .meta description layer to check which touched ids have descriptions.
meta_path = os.path.join(".smith", "index", "files", rel + ".meta")
desc_ids = set()
if os.path.isfile(meta_path):
    in_funcs = False
    current_id = None
    with open(meta_path, "r", encoding="utf-8") as fh:
        for line in fh:
            line = line.rstrip("\n")
            if line.startswith("## Functions") or line.startswith("## Classes"):
                in_funcs = True
                current_id = None
                continue
            if line.startswith("## ") and in_funcs:
                in_funcs = False
                current_id = None
                continue
            if in_funcs:
                m = re.match(r"^\s*Id:\s+(\S+)", line)
                if m:
                    current_id = m.group(1)
                    continue
                m = re.match(r"^\s*Description:\s+(.+)$", line)
                if m and current_id:
                    if m.group(1).strip():
                        desc_ids.add(current_id)
                    current_id = None

for fid, qname in touched:
    if fid not in desc_ids:
        print(f"- {qname} (id: {fid})")
PY
done < /tmp/smith-build-changed.txt
```

If `/tmp/smith-build-coverage-misses.txt` is non-empty, include a
**"Description Coverage Warnings"** section in the PR body (see Step 5.4
template). If empty, omit the section entirely.

This is a FLAG, never a blocker. Always proceed with PR creation. If
`git diff "$BASE_BRANCH"` returns no files (clean tree, target branch ahead), the
section is a no-op. Per data-model.md §9.3.

Include a **"Clean Code Review"** section in the PR body when
`/tmp/smith-build-clean-code-findings.txt` (written by Phase 3.5) is
non-empty:
```bash
[ -s /tmp/smith-build-clean-code-findings.txt ] && echo "include section" || echo "omit section"
```
If empty, omit the section entirely — matching the same include-if-non-empty
pattern as the two scans above. This is a FLAG, never a blocker. Always
proceed with PR creation.

Include a **"Security Review"** section in the PR body when
`/tmp/smith-build-security-findings.txt` (written by Phase 3.6, which already ran
pre-commit — before Phase 4 or Phase 5 began) is non-empty:
```bash
[ -s /tmp/smith-build-security-findings.txt ] && echo "include section" || echo "omit section"
```
If empty, omit the section entirely — same include-if-non-empty pattern as the
scans above. This is a FLAG, never a blocker. Always proceed with PR creation.

### 5.4 Create PR & Merge
```bash
BASE_BRANCH=$(.specify/scripts/bash/get-base-branch.sh)
gh pr create --base "$BASE_BRANCH" --title "<short title>" --body "$(cat <<'EOF'
## Summary
<bullet points from release notes>

## Test plan
<from test results>

## File Size Warnings
<contents of /tmp/smith-build-oversized.txt if non-empty; otherwise omit this section>

The following files exceed the 300-line threshold and should be considered
for decomposition in follow-up work:

<oversized file list, e.g.:>
- `backend/src/api/v1/products.py` — 1,250 lines (exceeds 300)
- `services/billing/main.py` — 487 lines (exceeds 300)

## Description Coverage Warnings
<include this section only when /tmp/smith-build-coverage-misses.txt is non-empty>

<N> methods in this diff lack `.meta` descriptions:
<bullet list from /tmp/smith-build-coverage-misses.txt, e.g.:>
- backend/src/services/webhook.py::WebhookRetryHandler::backoff (id: 4b8d6e2a9f1c0e7d)
- backend/src/services/webhook.py::WebhookRetryHandler::dead_letter (id: a3f0c8d2e7b14955)
- frontend/src/lib/api/products.ts::fetchProductBundle (id: 9c1d4e0a8f2b5c63)

Run `/smith-index --describe --system <name>` to backfill before merge,
or rely on the next `/smith-bugfix`/`/smith-new` workflow to update
descriptions for touched methods in-context.

## Clean Code Review
<include this section only when /tmp/smith-build-clean-code-findings.txt is non-empty>

<contents of /tmp/smith-build-clean-code-findings.txt verbatim, e.g.:>
- **[High]** `services/billing/webhook.py:142` — God function mixes request
  validation, HTTP retry logic, and dead-letter persistence in one 80-line
  method (tenet: Mixed responsibilities)
- **[Medium]** `frontend/src/lib/api/products.ts:58` — Near-duplicate of
  `fetchOrderBundle`'s pagination-assembly logic (tenet: Duplicated logic)
+ 2 low-severity notes

This is a FLAG, never a blocker. Always proceed with PR creation.

## Security Review
<include this section only when /tmp/smith-build-security-findings.txt is non-empty>

<layer-disclosure line derived from /tmp/smith-build-security-layers-ran.txt — MUST
name every layer ran vs skipped-absent, never imply full coverage when a tool was
absent, e.g.:>
Layers: built-in secret scan ✓, gitleaks (absent), semgrep (absent), bandit (absent),
LLM review ✓.

<contents of /tmp/smith-build-security-findings.txt verbatim, e.g.:>
- **[High]** `services/billing/webhook.py:142` — Matches AWS access-key-id pattern
  `aws-akia` (excerpt: AKIA********Z9Q1) (category: Secrets/credential handling,
  layer: 1)
- **[Medium]** `services/api/db.py:12` — Query built via string concatenation from a
  request parameter (category: Injection (SQL), layer: 2)
+ 3 low-severity notes

This is a FLAG, never a blocker for a non-terminating finding. Always proceed with PR
creation.

## Release notes
See specs/<feature>/release.md

Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```

Then merge the PR. **IMPORTANT**: Always run `gh pr merge` from the **primary repo directory**, not from a worktree. Running from a worktree causes "fatal: 'main' is already checked out" errors.
```bash
# If in worktree mode:
cd <PRIMARY_REPO> && gh pr merge <pr-number> --squash --delete-branch
# If in normal mode:
gh pr merge <pr-number> --squash --delete-branch
```

### 5.5 Return to the base branch

**Normal mode:**
```bash
BASE_BRANCH=$(.specify/scripts/bash/get-base-branch.sh)
git checkout "$BASE_BRANCH"
git pull origin "$BASE_BRANCH"
```

**Worktree mode:** Do NOT run `git checkout "$BASE_BRANCH"` — the base branch is already checked out in the primary repo. Instead, proceed directly to Phase 6. The worktree cleanup in Phase 7 handles branch deletion.

## Phase 6: Service Rebuild

After merging to the base branch:

1. **Identify affected services** from the changed files
2. **Docker-touching detection** (worktree mode only):
   - Check if changes include `docker-compose.yml`, `Dockerfile`, or service build contexts
   - If Docker-touching: display warning:
     > "This feature modifies Docker configuration. Worktree isolates git only — Docker operations will affect running containers."
   - Proceed with Docker operations after warning.
3. **Rebuild each affected service** — always run from the **primary repo directory** (not the worktree), since Docker Compose resolves paths relative to the compose file and Colima cannot mount `/tmp`:
   ```bash
   # If in worktree mode: pull changes to primary repo first
   cd <PRIMARY_REPO> && git pull origin "$(.specify/scripts/bash/get-base-branch.sh)"
   docker compose up -d --build <service-name>
   ```
   ```bash
   # Normal mode:
   docker compose up -d --build <service-name>
   ```
4. **Copy `.env`**: If in worktree mode and Docker operations are needed, ensure `.env` exists in the primary repo (it always should — this is a safety check).
5. **Run health check**:
   ```bash
   bash scripts/health-check.sh
   ```
6. If any service is unhealthy: attempt restart, log issue

## Phase 7: Release Notes & Summary

### 7.1 Generate Release Notes

Write `specs/<feature>/release.md`:

```markdown
# Release: [Feature Name]

**Date**: [YYYY-MM-DD]
**Branch**: [branch-name]
**PR**: [#number](link)
**Spec**: [spec.md](spec.md)

## Summary

[2-3 sentence description of what was built]

## Changes

### Files Created
| File | Purpose |
|------|---------|
| path/to/file.tsx | Description |

### Files Modified
| File | Change |
|------|--------|
| path/to/file.tsx | What changed |

### System Specs Updated
| Spec | Changes Recorded |
|------|-----------------|
| system-15-command-center/spec.md | Description |

## Testing

### Unit Tests
- [PASS/FAIL] pnpm test — X tests passed
- [PASS/FAIL] poetry run pytest — X tests passed

### E2E Tests (if applicable)
- [PASS/FAIL] Existing Playwright suite — X tests passed
- [PASS/FAIL] New Playwright tests — X tests for [flows tested]

### Known Issues
- [Any test failures that couldn't be resolved]

## Deviations from Spec

[Any differences between what was spec'd and what was implemented, with reasoning]

## Infrastructure

- Docker services rebuilt: [list]
- Health check: [PASS/FAIL]
```

### 7.2 Commit Release Notes
```bash
git add specs/<feature>/release.md
git commit -m "docs: add release notes for <feature>"
git push origin main
```

### 7.3 Worktree Cleanup (if applicable)

If `WORKTREE_MODE=true`:
1. **On success**: Remove the worktree from the **primary repo directory**:
   ```bash
   cd <PRIMARY_REPO> && git worktree remove <WORKTREE_PATH>
   ```
2. **On failure**: Preserve the worktree for debugging. Log the worktree path to the vault session log:
   > "Worktree preserved at <WORKTREE_PATH> for debugging. Clean up with: `git worktree remove <WORKTREE_PATH>`"

### 7.4 Clear Workflow Tracking

Remove the active-workflow file to signal the workflow is complete. Use the shipped helper, which coexists with a broad `Bash(rm:*)` deny rule:
```bash
.specify/scripts/bash/clear-active-workflow.sh "$BRANCH"
```

### 7.4.1 Post-Workflow Reflection

After workflow completion (success or failure), trigger a Ledger reflection if enabled:

1. Read `.smith/config.json` — if `ledger.auto_reflect` is `true` (default), proceed
2. Launch a **non-blocking** background sub-agent using the configured reflection model (default: Haiku):
   - Pass: current session log path, `.smith/vault/ledger/` path
   - The sub-agent runs the `smith-reflect` workflow
   - Do NOT wait for the sub-agent to complete
3. If `.smith/config.json` is missing or `ledger.auto_reflect` is `false`, skip silently

### Post-Reflection Reconciliation Check

After reflection completes (or is skipped):

1. Read `.smith/config.json` — if `ledger.reconcile.auto_reconcile` is `false`, skip
2. Read `.smith/vault/ledger/.meta.json` — check signals against thresholds:
   - `estimated_tokens > thresholds.total_tokens_max` (default 30000)
   - `context_budget_violations > thresholds.context_violations_threshold` (default 3)
   - `reinforcements_since_reconcile > thresholds.reinforcements_threshold` (default 50)
3. Check minimum interval: if `last_reconcile` is less than `minimum_hours_between_reconciles` (default 6) hours ago, skip
4. If any threshold exceeded AND minimum interval has passed:
   - Launch a **non-blocking** background sub-agent using the configured `reconcile_model` (default: Haiku)
   - Pass: "Run /smith-ledger reconcile on this project"
   - Do NOT wait for the sub-agent to complete
5. If no threshold exceeded, `.meta.json` is missing, or config is missing, skip silently

### 7.5 Display Summary

Output to the user:
- Feature name
- PR link
- Release notes summary (inline, not just a file link)
- Any issues requiring manual attention
- Link to full release notes file
- Confirmation that we're back on `main` with services healthy

## Recovery Mode

If `/smith-build` is run manually (not from `/smith-new`):

1. Detect current state by checking:
   - Which branch we're on
   - Whether tasks.md exists and has incomplete tasks
   - Whether code changes exist but aren't committed
   - Whether a PR exists but isn't merged

2. Resume from the appropriate phase:
   - No tasks.md → start from Phase 1
   - Tasks partially complete → resume Phase 2 from first incomplete task
   - All tasks complete, uncommitted → start from Phase 5
   - PR exists, not merged → start from Phase 5.4
   - PR merged, services not rebuilt → start from Phase 6
   - Everything done → just generate release notes (Phase 7)

## Key Rules

- ALL phases run without user interaction — a Phase 3.6 security hard-stop is itself
  prompt-free (a logged, autonomous termination decision), never a subagent failure,
  and must not be swept into the "retry once" bullet below
- Use subagents for each major phase to manage context
- If a subagent fails, retry once before logging the error and continuing
- Always rebuild Docker after code changes — never skip this
- Never use `git add -A` or `git add .` — always stage specific files
- Never commit `.env` files or credentials
- Playwright tests are MANDATORY when frontend files are modified
- The release.md file is the permanent record of what was built