Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ sequencing**. Forge gives you:
carry from one repo to the next, so your mistake rate drops over time.
- **A dynamic workflow engine** — run arbitrary **parallel agent DAGs** beyond the fixed
pipeline, opt-in (new in v0.4.0).
- **Docker and tooling preflight** — advisory Docker hygiene checks and a declarative CLI
registry (`docker`, `gh`, …) that detects what's missing and offers to install it, only
after you confirm (new in v0.7.0).

Nothing advances silently and nothing is untraceable. When you *do* need to skip a gate,
`/forge:force-advance` records **why** — the override is explicit and audited.
Expand Down Expand Up @@ -343,6 +346,7 @@ See [`references/daemon-bus.md`](references/daemon-bus.md).
| `/forge:status` | Current stage, task, blockers, daemon status, recent history |
| `/forge:resume` | Restore context after a session restart |
| `/forge:doctor` | Diagnose environment / plugin / gate health and name the fix |
| `/forge:preflight` | Detect missing required CLIs (docker, docker compose, gh) and offer to install one, only after you confirm — see [`references/docker-and-tooling.md`](references/docker-and-tooling.md) |
| `/forge:why` | Explain a gate criterion, lesson tag, stage, or current blocker |
| `/forge:retro` | Cycle-completion retrospective after Stage 12 |
| `/forge:set-profile` | Set the project-type profile |
Expand Down
1 change: 1 addition & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ producing all 12 stage artifacts with traceability intact. ✅
| **v0.5.0** | 🟢 released | **Unified `~/.forge` graduation layer** — generalizes the T-022 lesson promoter into one tier-agnostic core (`scripts/_graduation.py`: registry · atomic IO · 30-day TTL · idempotent keyed merge · `Tier` protocol · fail-soft `graduate()` driver), then adds **skills** and **workflows** tiers behind **per-tier gates** (breadth for lessons; approved + ExpeL `weight>0` + `use≥2` for skills; validates-clean + ≥2 successful `workflow_run` records for workflows). **Project-wins** recall in every tier; skill recall = **symlink** (ADR-009), workflows recall via the loader search path. Automatic, silent, fail-soft at session-start (`FORGE_NO_GRADUATE=1` escape); new `/forge:graduate` (dry-run / list / scan). Behavior-preserving for lessons. ADR-008 + ADR-009. Tag `v0.5.0`, both remotes. SRS `build/01-srs/srs-v0.5.0.md`, DAG `build/04-plan/task-dag-v0.5.0.md` (T-207–T-213). |
| **v0.6.0** | 🟢 released | **Engine made real I — per-node session reuse** (cost-reduction, **zero default behavior change**). Drives the already-built-but-unused `_background_agent.dispatch(resume=...)` path from the v0.4.0 DAG engine: capture each node's first-attempt `session_id` and `--resume` it into **that same node's** retry/heal re-dispatches (same prompt + model), lowering their realized floor from `FRESH_FLOOR_USD` `$0.06` to `RESUME_FLOOR_USD` `$0.01`. **Within-node only**; the independent verifier is **never** reused (REQ-WF-002); admission stays on `FRESH_FLOOR_USD` so the estimator/admitted-set split is identical on vs off (AC-WF-014); a stale session **falls back to fresh** within the attempt budget. Opt-in `orchestration.session_reuse` (strict `is True`, default off ⇒ byte-identical to v0.4.x). ADR-010. **Deferred follow-ups**: per-branch/cross-node reuse (trio item 1's other half, measurement-gated), trio items 2–3 (top-level generation · pipeline-as-WorkflowSpec), and **caveman mode** (orthogonal prompt-compression, candidate v0.6.1). ADR-010. Tag `v0.6.0`, both remotes. SRS `build/01-srs/srs-v0.6.0.md`, DAG `build/04-plan/task-dag-v0.6.0.md` (T-214–T-219). |
| **v0.6.1** | 🟢 released | **Caveman mode — measured and rejected; static prompt tightening only** (**zero default behavior change**). Investigated the `caveman` token-reduction approach: the one stdlib-legal lever (a terse-output preamble at the dispatch chokepoint) was built behind a default-off `orchestration.caveman_mode` toggle and **measured** against the `_cost_cap` output-token ledger — a real before/after on the Dreamer free-prose prompt (`haiku`, N=5/arm) showed **mean −52.9%**, no saving, well under the REQ-CM-005 ≥10% gate (Forge's free-prose prompts are already length-bounded). Per the gate the **runtime toggle was dropped** (config + `_caveman.py` + wiring reverted; dispatch byte-identical to v0.6.0); v0.6.1 ships only the **deterministic static tightening** of verbose non-verdict prompt constants (`dreamer`/`autopilot`/`parallel_build`) — verify/skeptic/gate/observer untouched. The measurement gate worked as designed. ADR-011. Tag `v0.6.1`, both remotes. SRS `build/01-srs/srs-v0.6.1.md`, DAG `build/04-plan/task-dag-v0.6.1.md` (T-220–T-226). |
| ~~v0.7.0~~ | ⚪ folded | **Docker workflow enforcement + extensible tooling preflight** (fail-soft, never-block) — a declarative tool registry (`references/tool-registry.md`) + `scripts/tool_preflight.py` (detect via `shutil.which` + version probe, cached like the capability probe), an advisory `scripts/check_docker_readiness.py` wired **unconditionally** into `/forge:deploy` (cross-cutting — every project gets it, regardless of profile), the sole confirm-then-install surface `/forge:preflight`, and an opt-in `docker` profile (never auto-assigned over a real app type). ADR-012. Built as T-227–T-233 but **folded into the v0.8.0 release** alongside Builder Pro rather than tagged separately. SRS `build/01-srs/srs-v0.7.0.md`, DAG `build/04-plan/task-dag-v0.7.0.md`. |

---

Expand Down
111 changes: 111 additions & 0 deletions build/02-architecture/adr/012-docker-and-tooling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# ADR-012: Docker Workflow Enforcement — Cross-Cutting Handling + an Opt-In Profile, Advisory and Never-Blocking, with a Declarative Tool Registry

**Status**: Accepted
**Date**: 2026-06-24

## Context

Forge can manage a containerized project, but until now had no awareness of Docker as a
workflow: no check that a project's `Dockerfile`/compose is hygienic (pinned base image,
healthcheck, non-root user, `.dockerignore`), and no verification that the CLIs a workflow
needs — `docker`, `docker compose`, `gh` for git/GitHub flows — are actually installed. A user
could hit a wall at the deploy or release stage because a required tool was missing, with no
early signal and no guided install. There was also no first-class way to tell Forge "this is a
Docker project."

Two constraints bound the design, both confirmed against the code before implementation:

1. **Docker is orthogonal to the profile cascade.** `scripts/detect-project-type.py:detect()`
returns exactly one mutually-exclusive `type`, so a containerized FastAPI app classifies as
`api`. A literal "9th docker profile" in the auto-detect cascade would only ever fire for
pure-infra repos — the common "app that ships a container" case would get **no** Docker
treatment. Docker handling must therefore be **cross-cutting**, not gated on a profile.
2. **Hooks run non-interactively.** A hook can detect a missing tool and surface a message, but
it cannot pop an installer. Running an install (`brew`/`apt`/`winget`) must happen in a
**skill turn** where the user can confirm. So detection and offering-to-install are separate
surfaces.

## Decision

**Ship a fail-soft, never-block Docker-and-tooling layer**: a declarative, extensible tool
registry with detection cached like the existing capability probe; an advisory Docker hygiene
check wired cross-cutting into deploy; and an opt-in `docker` profile that never auto-overrides
a real app type.

Four load-bearing decisions:

- **Cross-cutting, not a mutually-exclusive profile.** Because `detect()` returns one type and
Docker is orthogonal, Docker hygiene + tool checks attach to **any** project — via the
self-no-op'ing `scripts/check_docker_readiness.py` run unconditionally at deploy
(`skills/forge-deploy/SKILL.md`), and the tool registry's `docker_artifacts_present`
condition — rather than only to a `docker`-typed project. The `docker` profile
(`references/project-type-profiles.md`) is an **opt-in overlay** (stage emphasis + the
advisory hygiene criterion) and a **suggestion-only** hint from `detect-project-type.py`
(`suggested_profile: "docker"`, only when the type is otherwise `unknown` and Docker
dominates) — it never auto-overrides a real app type.
- **Advisory, never block.** The hygiene check is **agent-surfaced** (the deploy skill relays
its `WARN:` lines, and the profile's `additional_criteria` entry documents the concern for
the stage agent), **not** a mechanical `gate-criteria.md` entry — so it can never fail a gate.
`check_docker_readiness.py` exits 0 even when it finds problems.
- **Detect in a hook; install in a skill.** `hooks/session-start.py` and `scripts/doctor.py`
only ever detect and report a missing tool. `skills/forge-preflight/SKILL.md` is the **sole**
surface that runs an installer, and only after the user explicitly confirms which tool to
install. Auto-install was considered and rejected.
- **Declarative + extensible registry.** Tools live as data in
`references/tool-registry.md` — adding a new required CLI is a registry entry, not new code.
The detection/cache/TTL machinery reuses the capability-probe pattern
(`hooks/_background_agent.py`: `shutil.which` + `.forge/*.json` + 24h TTL + detached refresh,
T-138) rather than inventing a second one.

## Rationale

1. **The common case gets coverage without misclassification.** Cross-cutting hygiene means a
Dockerized FastAPI app is checked for Docker hygiene *and* still gets the `api` profile's
spec/eval treatment — neither is sacrificed for the other.
2. **Nothing in this layer can regress an existing project.** Every script here is fail-soft and
exits 0; a project with no Docker artifacts and all tools present is byte-identical in
behavior to the pre-v0.7.0 baseline (no advisory output, no new blocking path).
3. **Consent before any install.** The non-interactive/interactive split (detect in a hook,
install only in a confirmed skill turn) is the only way to offer installs without ever running
a command the user hasn't seen and approved.
4. **One detection pattern, not two.** Reusing the capability-probe's cache/TTL/detached-refresh
shape for tool status keeps the hot-path cost (a session-start hook read) identical in kind to
an already-shipped, already-measured pattern.

## Alternatives considered

- **A literal 9th cascade profile (`docker` as a mutually-exclusive `type`).** Rejected: would
only fire for pure-infrastructure repos with no other signal; the common "app that ships a
container" case (a Dockerized API, a Dockerized fullstack app) would get no Docker treatment
at all, since `detect()`'s cascade already classifies those as `api`/`fullstack`.
A hybrid two-path design was also considered and rejected as YAGNI for the rare pure-infra
repo — the cross-cutting-plus-opt-in-overlay shape already covers it.
- **Auto-installing missing tools without confirmation.** Rejected during brainstorming — running
an installer non-interactively on a user's machine without consent is unacceptable regardless
of how the tool was detected as missing.
- **A mechanical, blocking Docker gate in base `gate-criteria.md`.** Rejected: this release's
Docker hygiene is explicitly advisory; a hard-coded blocking entry there would fail every
containerized project on day one for issues the user may not be ready to fix yet, contradicting
the never-block decision the rest of this layer is built around.
- **Generating Dockerfiles/compose/CI as the mandatory deploy output.** Not chosen — the deploy
agent may still author them, but Forge does not enforce a generated containerized deploy path
as part of this layer.

## Consequences

- New: `references/tool-registry.md` (declarative registry), `scripts/tool_preflight.py`
(detection + `.forge/tool-status.json` cache), `scripts/check_docker_readiness.py` (advisory
hygiene), `skills/forge-preflight/SKILL.md` (the sole install surface).
- Changed: `scripts/doctor.py` gains `check_required_tools`; `hooks/session-start.py` gains a
budget-aware tool advisory (dropped first under token pressure, ahead of lessons/rules — a
stricter budget posture than the existing capability/health/traceability notes, which are not
currently budget-counted); `skills/forge-deploy/SKILL.md` runs the hygiene check
unconditionally; `scripts/detect-project-type.py` emits `has_docker`/`docker_indicators` and
a `docker` suggestion hint; `references/project-type-profiles.md` gains `## Profile: docker`.
- Out of scope (recorded, not silently dropped): running agents **inside** Docker containers
(sandboxed execution — a separate, larger track, closest to the existing `worktree_isolation`
toggle); auto-installing tools without confirmation; a mechanical/blocking Docker gate; the
engine trio (`build/01-srs/srs-v0.6.0.md` §6) and the standing non-goals
(`build/01-srs/srs-v0.4.1.md` §5.4) — unchanged.
- See `references/docker-and-tooling.md` for the user-facing reference on the registry format,
the required-tool resolution rules, and the advisory hygiene checks.
45 changes: 45 additions & 0 deletions build/05-implementation/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,51 @@

## Decisions

## 2026-08-10 T-233 — Docker/tooling: cross-cutting handling + opt-in profile (ADR-012); v0.7.0 folded into v0.8.0

**Context**: Forge had no Docker workflow awareness — no hygiene check on `Dockerfile`/
compose, no verification that `docker`/`gh` were installed before a workflow needed them.
`detect-project-type.py:detect()` returns exactly one mutually-exclusive `type`, so a
literal 9th "docker" cascade profile would only ever fire for pure-infra repos — a
Dockerized FastAPI app would still classify as `api` and get no Docker treatment.
Separately, the user asked to complete v0.7.0 and release it together with the
already-merged v0.8.0 Builder Pro work rather than as its own tag.

**Decision**: Cross-cutting Docker handling (an advisory `check_docker_readiness.py` run
**unconditionally** at deploy, self-no-op without artifacts) plus an **opt-in** `docker`
profile that is suggestion-only and never auto-assigned over a real app type. A
declarative tool registry (`references/tool-registry.md`) + `tool_preflight.py` reuse the
existing capability-probe cache pattern (T-138); the only installer surface is
`/forge:preflight`, gated on explicit per-tool confirmation. v0.7.0 (T-227–T-233) is
**folded into the v0.8.0 release** rather than tagged separately — same precedent as
v0.3.2 folding into v0.3.3.

**Why**: Cross-cutting handling covers the common "app that ships a container" case
without misclassifying it; a mutually-exclusive profile would have missed it entirely.
Advisory-only (the hygiene script always exits 0) keeps the never-block guarantee that
every prior release in this program has held. Detect-in-a-hook / install-in-a-skill is the
only way to offer an install without ever running a command the user hasn't confirmed.
Folding the two versions into one release avoids a near-immediate second version bump for
work that landed in the same sitting.

**Alternatives considered**: A literal 9th cascade profile — rejected, would miss the
common containerized-app case. Auto-installing missing tools — rejected outright during
2026-06-24 brainstorming. A mechanical blocking gate in base `gate-criteria.md` for Docker
hygiene — rejected, contradicts the advisory/never-block design this layer is built
around. Tagging v0.7.0 separately before v0.8.0 — rejected per explicit user direction;
folding avoids churn for two versions released in the same sitting.

**Consequences**: New `references/tool-registry.md`, `scripts/tool_preflight.py`,
`scripts/check_docker_readiness.py`, `skills/forge-preflight/SKILL.md`. Changed:
`scripts/doctor.py` (`check_required_tools`), `hooks/session-start.py` (tool advisory,
dropped first under token pressure), `skills/forge-deploy/SKILL.md` (unconditional hygiene
check), `scripts/detect-project-type.py` (`has_docker`/`docker_indicators` + docker
suggestion), `references/project-type-profiles.md` (`## Profile: docker`). ADR-012.
`ROADMAP.md` marks v0.7.0 `⚪ folded`; the next release ships as **v0.8.0** covering both
Builder Pro (T-241–T-253) and this Docker/tooling layer (T-227–T-233).

---

## 2026-06-24 T-224 — Caveman runtime toggle DROPPED by the measurement gate; ship only static tightening

**Context**: v0.6.1 added an opt-in `orchestration.caveman_mode` that prepends a terse-output preamble
Expand Down
29 changes: 29 additions & 0 deletions build/05-implementation/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@

## Current State

- **v0.7.0 (Docker workflow enforcement + tooling preflight) COMPLETE — T-227..T-233.**
Fail-soft, never-block capability layer, orthogonal to the Builder Pro work below (no
shared files). M1 Detection core: T-227 `references/tool-registry.md` (declarative,
seeded `docker`/`docker compose`/`gh`) + `scripts/tool_preflight.py` (shutil.which +
optional version_probe, `.forge/tool-status.json` 24h-TTL cache, mirrors the T-138
capability-probe pattern) — 21 tests. T-228 `scripts/check_docker_readiness.py`
(advisory hygiene: pinned base image, HEALTHCHECK, non-root USER, `.dockerignore`,
compose parse+`services:` — always exits 0, even with findings) — 13 tests. M2
Surfacing: T-229 `doctor.py` `check_required_tools` (one warn per missing required
tool, install command as `fix`) — 5 tests. T-230 `session-start.py` tool advisory
(budget-aware, dropped **first** under token pressure, ahead of lessons/rules — a
stricter posture than the existing capability/health/traceability notes) — 10 tests.
T-231 `skills/forge-preflight/SKILL.md` (`/forge:preflight` — the sole surface that
runs an installer, only after per-tool user confirmation) — 6 tests. T-232 `##
Profile: docker` (opt-in, stage_3/8/9 overrides, never auto-assigned) +
`forge-deploy/SKILL.md` unconditional hygiene wiring + `detect-project-type.py`
`has_docker`/`docker_indicators`/`suggested_profile: docker` (FastAPI-in-Docker stays
`api`) — 23 tests across 5 files. T-233 (this entry): ADR-012, `references/
docker-and-tooling.md`, README (`/forge:preflight` row + capability bullet), ROADMAP
(v0.7.0 marked `⚪ folded` into v0.8.0), `decisions.md`. Full unit suite **2017 pass**
(baseline 1949 → +68 across T-227..T-232), `validate-plugin.py` exit 0. **Per user
direction: v0.7.0 is not tagged separately — it ships as part of the next release,
v0.8.0, alongside Builder Pro below.** SRS `build/01-srs/srs-v0.7.0.md`, DAG
`build/04-plan/task-dag-v0.7.0.md`. Worktree `.claude/worktrees/
recursive-wobbling-sky`, branch `feat/v0.7.0-docker-tooling`, branched from
`develop` (`4f74d32`). **NEXT**: full regression sweep (`full-pipeline.sh`), open the
PR against `develop`, then the combined v0.8.0 release (bump-version, CHANGELOG,
tag, both remotes).

- **v0.8.0 Revision 2 (Builder Pro) COMPLETE — T-241..T-253.** Revision 1 below
(T-235..T-240) was scoped from a secondary analysis doc and got the shape wrong
(three LLM sub-agent personas instead of one thin-router agent + a deterministic
Expand Down
Loading
Loading