feat(v0.7.0): Docker workflow enforcement + tooling preflight - #66
Merged
Conversation
Adds references/tool-registry.md (declarative markdown-with-YAML, same shape as gate-criteria.md/project-type-profiles.md) seeded with docker, docker compose, and gh -- name/which/version_probe/workflows/stages/ required_when/per-OS install per entry. A new tool is a data entry, not code. scripts/tool_preflight.py: pure detection (shutil.which + optional version_probe -- a present binary whose specific capability probe fails, e.g. docker without the compose plugin, counts as absent), cached to .forge/tool-status.json with a 24h TTL, mirroring hooks/_background_agent.py's capability-probe pattern (T-138). required_when resolves against project state (Docker artifacts present via a tree walk / current_stage == 12 via pipeline/state.md). Never raises: malformed registry entries are skipped not fatal, an unreadable registry/cache degrades to empty/no-op, _state_lib's SystemExit-on-missing-state.md is caught explicitly. install_command() only ever returns a string -- verified by a test that fails the suite if subprocess.run is ever called. TDD red-first: 21 tests confirmed failing on ModuleNotFoundError before implementation, all green after. Full unit suite 1970 pass, zero regressions. Ref: T-227
Mirrors check_store_readiness.py's no-op-when-absent template, but purely advisory: exits 0 whether or not Docker artifacts are found, and exits 0 even when it reports findings (WARN: lines) -- never a blocking gate. Per Dockerfile: base image pinned (digest pin or a reference to an earlier build stage both count as pinned, only an unpinned/`:latest` registry image flags), HEALTHCHECK present, last USER directive isn't root. Project-level: .dockerignore exists, each compose file parses and has a top-level services: key (fail-soft without PyYAML -- skips, doesn't crash). Unreadable files are skipped, not fatal. TDD red-first: 13 tests confirmed failing on ModuleNotFoundError, all green after implementation. Full unit suite 1983 pass, zero regressions. Ref: T-228
Adds check_required_tools(forge_root, cwd) -> list[CheckResult]: one warn-level result per missing required tool_preflight tool, with the resolved install command as the fix -- a present or not-required tool produces no result. Wired into run_checks(). Doctor stays read-only: it surfaces the install command, it never runs it. Never raises -- a tool_preflight error degrades to no results, same posture as every other doctor check. +5 tests. Full unit suite 1988 pass, zero regressions. Ref: T-229
Adds _ensure_tool_status() (mirrors _ensure_capabilities: detached `tool_preflight.py refresh` subprocess, fired only when .forge/tool-status.json is missing or older than the 24h TTL, never raises) and _tool_preflight_block() (one advisory line per missing required tool, read from the cached JSON with stdlib only -- '' when nothing missing, no cache, or an unreadable cache). Restructures the budget cascade so the tool block is dropped FIRST under token pressure, before lessons trim and the rules drop (REQ-TR-004, AC-TR-003) -- _compose() gained a tool_text parameter threaded through the same three-step fallback that already existed for lessons/rules. +10 tests. Full unit suite 1998 pass, zero regressions. Ref: T-230
New skills/forge-preflight/SKILL.md (name: preflight, allowed-tools: [Read, Bash]): runs tool_preflight.py check, presents each missing required tool with its per-OS install command (sourced from the registry, never invented), and runs a command via Bash only after the user explicitly confirms that specific tool. Declined tools are never installed. The single "offer to install" surface in Forge -- session-start and doctor.py only ever detect and report. TDD red-first: 6 structural tests confirmed failing (file missing) before the skill existed, all green after -- frontmatter parses (avoids the 2026-06-22 ': ' YAML trap), allowed-tools correct, documents detect->confirm->install, states it never auto-runs. Full unit suite 2004 pass, zero regressions. Ref: T-231
references/project-type-profiles.md: new `## Profile: docker` block -- stage_emphasis high on architecture/deploy; stage_3 additional_concerns (multi-stage builds, base-image pinning, build secrets/volumes, layer caching); stage_8 additional_steps (build/scan/push) + additional_criteria G8-DOCKER-001 -> check_docker_readiness.py (severity: warning); stage_9 additional_concerns (container restart/ OOM-kill monitoring, resource alerting) added to satisfy the T-024 >=3-stage-override invariant so docker joins the "all standard profiles parse" parity test. Opt-in and never a precondition for the cross-cutting Docker handling that already runs unconditionally (T-228/forge-deploy). skills/forge-deploy/SKILL.md: runs check_docker_readiness.py --cwd . unconditionally in pre-flight (every project, regardless of profile, self-no-ops without Docker artifacts) and relays WARN: findings as advisory -- never blocking, never gating the stage. scripts/detect-project-type.py: _finalize() now emits has_docker + docker_indicators (cross-cutting, applied regardless of `type`, a FastAPI-in-Docker project stays `api`) and adds suggested_profile: "docker" only when type is unknown and Docker artifacts are present with nothing else matched (mirrors the existing script-suggestion pattern, never auto-assigned over a real app type). set-profile.py and load-profile.py needed no code changes -- both already discover profiles dynamically from project-type-profiles.md. +23 tests across 5 files (detect-project-type, set-profile, load-profile parity, deploy-skill structural). Full unit suite 2017 pass, validate-plugin.py exit 0, zero regressions. Ref: T-232
ADR-012 (build/02-architecture/adr/012-docker-and-tooling.md): records the cross-cutting-handling + opt-in-profile architecture, advisory never-block hygiene, detect-in-hook/install-in-skill split, and the declarative registry decision, with alternatives considered and consequences. references/docker-and-tooling.md: user-facing reference for the registry format, detection/cache, surfacing, /forge:preflight, hygiene checks, and the docker profile. README.md: new capability bullet + /forge:preflight command row. ROADMAP.md: v0.7.0 marked `~~v0.7.0~~ | folded` -- built as T-227-233 but folded into the v0.8.0 release alongside Builder Pro rather than tagged separately (same precedent as v0.3.2 folding into v0.3.3), per explicit user direction. progress.md: v0.7.0 COMPLETE row (T-227..T-233), full test/commit trail. decisions.md: new entry recording the architecture decision and the fold-into-v0.8.0 release decision. Full unit suite 2017 pass, validate-plugin.py exit 0, full-pipeline.sh 12/12 gates + traceability intact. Ref: T-233
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements v0.7.0 (T-227..T-233) from
build/01-srs/srs-v0.7.0.md-- a fail-soft,never-block Docker-and-tooling layer, orthogonal to the Builder Pro work already on
develop(no shared files). See ADR-012 for the full design.references/tool-registry.md(declarative, seededdocker/docker compose/gh) +scripts/tool_preflight.py(detection +.forge/tool-status.jsoncache, mirrors the T-138 capability-probe pattern).
scripts/check_docker_readiness.py-- advisory hygiene check (pinnedbase image, HEALTHCHECK, non-root USER,
.dockerignore, compose validity). Alwaysexits 0, even with findings.
doctor.pygainscheck_required_tools(one warn per missing requiredtool).
session-start.pytool advisory -- budget-aware, dropped firstunder token pressure, ahead of lessons/rules.
/forge:preflight-- the sole surface that runs an installer, onlyafter per-tool user confirmation.
dockerprofile (never auto-assigned over a real app type) +unconditional cross-cutting hygiene wiring into
/forge:deploy+detect-project-type.pyhas_docker/docker_indicators/suggestion.references/docker-and-tooling.md, README/ROADMAP/progress/decisions docs.
Not tagged as v0.7.0 separately -- per user direction, this folds into the next
release, v0.8.0, alongside the already-merged Builder Pro work (same precedent
as v0.3.2 folding into v0.3.3).
ROADMAP.mdreflects this.Test plan
implementation existed
scripts/validate-plugin.py: exit 0tests/integration/full-pipeline.sh: 12/12 gates + traceability intact🤖 Generated with Claude Code