Skip to content

CLI Reference — `scripts/forge-bootstrap.py`

CLI Reference — scripts/forge-bootstrap.py

Section titled “CLI Reference — scripts/forge-bootstrap.py”

The deterministic core of forge-bootstrap: the greenfield gate, template composition, config write, the resume sentinel, toolchain probing, lint/test, and the baseline commit. Stdlib-only Python 3, no third-party packages.

The skill body invokes it via the portable-root prelude, e.g.:

Terminal window
R="$(bash -c 'for d in "${CLAUDE_PLUGIN_ROOT:-}" "$HOME"/.claude/skills/feature-forge "$HOME"/.claude/plugins/*/feature-forge; do [ -x "$d/scripts/forge-root.sh" ] && exec "$d/scripts/forge-root.sh"; done')"
[ -n "$R" ] || { echo "feature-forge: cannot locate plugin root" >&2; exit 1; }
python3 "$R/scripts/forge-bootstrap.py" <subcommand> "<target-dir>" --json [...]

<target-dir> is the project being bootstrapped (default .), distinct from $R (the plugin root). The helper resolves its own template directory from __file__, so no environment variable is needed.

CodeMeaning
0success: eligible / green / committed-or-staged
1findings: gate refusal (eligible:false) or verify not-green
2usage or I/O error — including verify toolchain-missing
  • Exit 1 prints the result JSON (a CheckResult or VerifyResult) on stdout — there are findings to act on.
  • Exit 2 prints a plain Error: … line on stderr with empty stdout — a usage/IO failure, or the distinct verify “toolchain missing” terminal outcome.

--json is accepted on every subcommand and selects machine-readable output.

check — greenfield gate + recovery detection

Section titled “check — greenfield gate + recovery detection”
forge-bootstrap.py check <target-dir> [--specs-dir ./specs] [--json]

Read-only. Walks the target’s top-level entries and allows only repo-meta: .git/, the configured specs directory, the sentinel, and files matching the allowed-meta pattern (README / LICENSE / .gitignore). Anything else is disqualifying. If this tool’s own .forge-bootstrap.json sentinel is present, it is surfaced as resumeMarker and the run is not treated as a refusal.

Returns CheckResult:

{ "eligible": true, "disqualifying": [], "resumeMarker": null }

Exit 0 when eligible (or resumable); exit 1 on refusal with disqualifying[] populated. Touches no files.

scaffold — emit the pipeline-ready baseline

Section titled “scaffold — emit the pipeline-ready baseline”
forge-bootstrap.py scaffold <target-dir> --answers '<JSON>' [--json]

--answers is the resolved Answers payload (required). Writes the sentinel first, runs git init if no .git/ exists, composes each member’s stack templates with token substitution, emits the repo-hygiene files (README, LICENSE unless license=="none", AGENTS.md always, CLAUDE.md when host=="claude"), writes forge.config.json, and emits CI when ci is true. Every written path is recorded in the sentinel’s artifactsWritten[]; a path already recorded is skipped (idempotent resume), and a pre-existing allowed-meta file is kept untouched.

--json prints { "artifactsWritten": [...] }. Exit 0 on success; 2 on IO error (missing template dir, unwritable target, malformed --answers, git init failure).

verify — toolchain detection + lint/test

Section titled “verify — toolchain detection + lint/test”
forge-bootstrap.py verify <target-dir> --answers '<JSON>' [--json]

Probes the chosen stack(s)’ toolchain (command -v), then runs each member’s resolved typeCheckCommand (lint) and testCommand from STACK_COMMANDS, in that member’s directory. Returns VerifyResult:

{
"green": true,
"toolchainPresent": true,
"lint": [{ "command": "mypy .", "ok": true, "member": "." }],
"test": [{ "command": "pytest", "ok": true, "member": "." }]
}

Each CommandOutcome.member is the member’s path ("." for a single package, e.g. packages/api for a monorepo member). green is true iff toolchainPresent and every outcome ok. Exit 0 green; 1 not-green (toolchain present, a command failed); 2 toolchain missing (toolchainPresent:false) — the false-green guard the Mode B gate depends on.

forge-bootstrap.py commit <target-dir> --answers '<JSON>' [--stage-only] [--json]

Follows the Git Commit Protocol. Stages exactly sentinel.artifactsWritten[] with git add -- <paths> (never git add -A, --force, or --no-verify), after removing the sentinel so it never enters history. The commit message prefix is read from the just-written forge.config.json commitPrefix (default forge). With --stage-only (or commitStyle == "stage-only") it leaves the scaffold staged with no commit. Returns CommitResult:

{ "committed": true, "commitHash": "a1b2c3d", "staged": ["..."], "sentinelRemoved": true }

Exit 0 on success; 2 on a git/IO failure (the sentinel is already removed, so the run is re-stageable).

forge-bootstrap.py status <target-dir> [--json]

Read-only. Emits the full Sentinel JSON (version, status, startedAt, mirrored answers, artifactsWritten[]) when .forge-bootstrap.json exists, or null when it does not. Exit 0 either way.

The skill body assembles one Answers object and passes it verbatim to scaffold/verify/commit:

FieldMeaning
projectNameproject name (default inferred from the target dir)
purposeone-line purpose (seeds README + config)
layout"single" or "monorepo"
licenseSPDX-ish id (MIT, Apache-2.0) or "none"
members[]one Member{name, path, stack, packageManager} per package (single → one at path:".")
modeB / modeBTargetMode B opt-in + "feature"/"epic"
ciemit a CI workflow
commitStyle"commit" or "stage-only"
authorLICENSE copyright holder (seeded from git config user.name, else project name)
host"claude" / "codex" / "other" / null — drives the host-conditional CLAUDE.md

Example (single Python package, commit, no Mode B):

{
"projectName": "acme-svc", "purpose": "Billing service",
"layout": "single", "license": "MIT",
"members": [{ "name": "acme-svc", "path": ".", "stack": "python", "packageManager": "uv" }],
"modeB": false, "modeBTarget": null, "ci": false, "commitStyle": "commit",
"author": "Ada Lovelace", "host": "claude"
}