From 4ef4dce71b7efccfedd2c02c395364c7bfe3366b Mon Sep 17 00:00:00 2001 From: NiftyAndy Date: Tue, 28 Jul 2026 13:36:52 -0400 Subject: [PATCH] feat: simplify initialization and synchronization --- .github/actions/setup/action.yml | 17 ++- .github/code-foundry.yml | 3 +- .github/code-foundry.yml.example | 4 +- .github/scripts/bootstrap.sh | 1 + .github/scripts/changed-files.sh | 2 +- .github/scripts/ci.sh | 1 - .github/scripts/codeql-languages.sh | 1 - .github/scripts/doctor.sh | 1 - .github/scripts/init-repo.sh | 23 +--- .github/scripts/profile.sh | 4 +- .github/scripts/security.sh | 1 - .github/scripts/sync-protection.sh | 1 - .github/scripts/sync-template.sh | 31 +---- .github/workflows/reusable-release.yml | 1 - README.md | 41 ++----- docs/CONFIGURATION.md | 93 +++++---------- docs/INITIALIZATION.md | 75 ++++-------- docs/WORKFLOWS.md | 3 +- src/cli.mjs | 154 ++++--------------------- 19 files changed, 109 insertions(+), 348 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index c8a35ed6..b344ad75 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -206,7 +206,6 @@ runs: javascript_package_manager=none configured_package_manager="" config_file=.github/code-foundry.yml - [ -f "$config_file" ] || config_file=.github/template.yml if [ -f "$config_file" ]; then configured_package_manager="$(awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file")" fi @@ -389,7 +388,7 @@ runs: if [ "$javascript" = true ] && task_language_needed javascript; then add_configured_tool node if { [ -f bun.lock ] || [ -f bun.lockb ] || - config_file=.github/code-foundry.yml; [ -f "$config_file" ] || config_file=.github/template.yml; awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file" 2>/dev/null | grep -qx bun; }; then + config_file=.github/code-foundry.yml; awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file" 2>/dev/null | grep -qx bun; }; then add_configured_tool bun fi fi @@ -403,7 +402,7 @@ runs: javascript) configured_tool node && mise_tools+=(node) if { [ -f bun.lock ] || [ -f bun.lockb ] || - config_file=.github/code-foundry.yml; [ -f "$config_file" ] || config_file=.github/template.yml; awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file" 2>/dev/null | grep -qx bun; } && configured_tool bun; then + config_file=.github/code-foundry.yml; awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file" 2>/dev/null | grep -qx bun; } && configured_tool bun; then mise_tools+=(bun) fi ;; @@ -451,12 +450,12 @@ runs: (inputs.cache-installed == 'true' || steps.profile.outputs.javascript_package_manager == 'pnpm' || steps.profile.outputs.javascript_package_manager == 'yarn') && - hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/code-foundry.yml', '.github/template.yml', '.mise.toml', 'mise.lock') != '' + hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/code-foundry.yml', '.mise.toml', 'mise.lock') != '' uses: actions/cache/restore@v5 with: path: | **/node_modules - key: ${{ runner.os }}-javascript-installed-v3-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/code-foundry.yml', '.github/template.yml', '.mise.toml', 'mise.lock') }} + key: ${{ runner.os }}-javascript-installed-v3-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/code-foundry.yml', '.mise.toml', 'mise.lock') }} - name: Restore Bun dependencies id: javascript-bun-cache-restore if: >- @@ -465,12 +464,12 @@ runs: inputs.cache-installed != 'false' && steps.profile.outputs.javascript_dependencies == 'true' && steps.profile.outputs.javascript_package_manager == 'bun' && - hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/code-foundry.yml', '.github/template.yml', '.mise.toml', 'mise.lock') != '' + hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/code-foundry.yml', '.mise.toml', 'mise.lock') != '' uses: actions/cache/restore@v5 with: path: | **/node_modules - key: ${{ runner.os }}-javascript-installed-v4-bun-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/code-foundry.yml', '.github/template.yml', '.mise.toml', 'mise.lock') }} + key: ${{ runner.os }}-javascript-installed-v4-bun-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/code-foundry.yml', '.mise.toml', 'mise.lock') }} - name: Detect Bun dependencies id: javascript-bun-dependencies if: >- @@ -737,7 +736,7 @@ runs: with: path: | **/node_modules - key: ${{ runner.os }}-javascript-installed-v4-bun-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/code-foundry.yml', '.github/template.yml', '.mise.toml', 'mise.lock') }} + key: ${{ runner.os }}-javascript-installed-v4-bun-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/.npmrc', '.github/code-foundry.yml', '.mise.toml', 'mise.lock') }} - name: Save installed JavaScript dependencies if: >- inputs.cache-save == 'true' && @@ -753,7 +752,7 @@ runs: with: path: | **/node_modules - key: ${{ runner.os }}-javascript-installed-v3-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/code-foundry.yml', '.github/template.yml', '.mise.toml', 'mise.lock') }} + key: ${{ runner.os }}-javascript-installed-v3-${{ hashFiles('**/package.json', '**/bun.lock', '**/bun.lockb', '**/pnpm-lock.yaml', '**/yarn.lock', '**/package-lock.json', '**/.npmrc', '**/.yarnrc*', '**/.pnpmfile.cjs', '.github/code-foundry.yml', '.mise.toml', 'mise.lock') }} - name: Save JavaScript packages if: >- inputs.cache-save == 'true' && diff --git a/.github/code-foundry.yml b/.github/code-foundry.yml index 247ba72b..caa973da 100644 --- a/.github/code-foundry.yml +++ b/.github/code-foundry.yml @@ -1,12 +1,11 @@ # Canonical Code Foundry repository configuration. version: 1 -template: code-foundry@latest profile: auto languages: typescript features: all package_manager: bun runtime_repository: 0xPlayerOne/code-foundry -runtime_ref: v0.22.0 +runtime_ref: v0.22.2 release_type: node npm_publish: true license: agpl-3.0-or-later diff --git a/.github/code-foundry.yml.example b/.github/code-foundry.yml.example index 9f3d197f..d8235005 100644 --- a/.github/code-foundry.yml.example +++ b/.github/code-foundry.yml.example @@ -1,5 +1,5 @@ -# Copy this file to .github/code-foundry.yml, edit the values you need, and run: -# npx code-foundry init --config .github/code-foundry.yml +# `npx code-foundry init` creates this file automatically. Edit it and run +# `npx code-foundry sync` whenever you want to change the baseline. # Omitted keys use automatic detection and the standard defaults. See # docs/CONFIGURATION.md for the complete visual reference. diff --git a/.github/scripts/bootstrap.sh b/.github/scripts/bootstrap.sh index 005bfeae..be06a944 100755 --- a/.github/scripts/bootstrap.sh +++ b/.github/scripts/bootstrap.sh @@ -4,6 +4,7 @@ set -euo pipefail git config core.hooksPath .githooks if command -v mise >/dev/null 2>&1; then + mise trust --yes .mise.toml >/dev/null 2>&1 || true if [ -f .mise.toml ] && [ ! -f mise.lock ]; then if MISE_TRUSTED_CONFIG_PATHS="$PWD" mise lock >/dev/null 2>&1; then printf '%s\n' 'Initialized mise.lock for deterministic CI tool installs.' diff --git a/.github/scripts/changed-files.sh b/.github/scripts/changed-files.sh index 3e6a8f2c..de119e0e 100755 --- a/.github/scripts/changed-files.sh +++ b/.github/scripts/changed-files.sh @@ -72,7 +72,7 @@ repo_foundry_pr_dependencies_unchanged() { while IFS= read -r file; do case "$file" in .github/workflows/security.yml|.github/scripts/security.sh|.github/scripts/changed-files.sh|\ - .github/actions/setup/action.yml|.github/code-foundry.yml|.github/template.yml|.mise.toml|mise.lock|\ + .github/actions/setup/action.yml|.github/code-foundry.yml|.mise.toml|mise.lock|\ .github/security-audit-allowlist.txt) return 1 ;; diff --git a/.github/scripts/ci.sh b/.github/scripts/ci.sh index f4bcc184..8060dd98 100755 --- a/.github/scripts/ci.sh +++ b/.github/scripts/ci.sh @@ -95,7 +95,6 @@ package_manager() { configured="$(bash .github/scripts/profile.sh get package_manager 2>/dev/null || true)" else config_file=.github/code-foundry.yml - [ -f "$config_file" ] || config_file=.github/template.yml if [ -f "$config_file" ]; then configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file")" fi diff --git a/.github/scripts/codeql-languages.sh b/.github/scripts/codeql-languages.sh index ab565afc..3d4f1282 100755 --- a/.github/scripts/codeql-languages.sh +++ b/.github/scripts/codeql-languages.sh @@ -27,7 +27,6 @@ if [ -x .github/scripts/profile.sh ]; then configured="$(bash .github/scripts/profile.sh get languages 2>/dev/null || true)" else config_file=.github/code-foundry.yml - [ -f "$config_file" ] || config_file=.github/template.yml if [ -f "$config_file" ]; then configured="$(awk -F': ' '/^languages:/ {print $2; exit}' "$config_file")" fi diff --git a/.github/scripts/doctor.sh b/.github/scripts/doctor.sh index 78570e63..1faea3bf 100755 --- a/.github/scripts/doctor.sh +++ b/.github/scripts/doctor.sh @@ -5,7 +5,6 @@ errors=0 configured_features="all" config_file=.github/code-foundry.yml -[ -f "$config_file" ] || config_file=.github/template.yml if [ -f "$config_file" ]; then configured_features="$(awk -F': ' '/^features:/ {print $2; exit}' "$config_file")" [ -n "$configured_features" ] || configured_features="all" diff --git a/.github/scripts/init-repo.sh b/.github/scripts/init-repo.sh index 47a7e971..fa010919 100755 --- a/.github/scripts/init-repo.sh +++ b/.github/scripts/init-repo.sh @@ -3,7 +3,6 @@ set -euo pipefail source="${REPO_FOUNDRY_SOURCE:-https://github.com/${GITHUB_REPOSITORY_OWNER:-OWNER}/code-foundry.git}" ref="main" -config_file="${REPO_FOUNDRY_CONFIG:-}" profile="${REPO_FOUNDRY_PROFILE:-auto}" protection=false dry_run=false @@ -67,7 +66,6 @@ Initialize or synchronize a repository from the shared baseline. Options: --source PATH_OR_URL Template source (default: REPO_FOUNDRY_SOURCE or GitHub owner) --ref REF Template branch or tag (default: main) - --config PATH Use a .github/code-foundry.yml configuration file --profile NAME auto, application, monorepo, or minimal --languages LIST auto or comma-separated: typescript,rust,python,solidity --features LIST all or comma-separated optional features: @@ -97,7 +95,6 @@ while [ "$#" -gt 0 ]; do case "$1" in --source) source="${2:?missing source path or URL}"; shift 2 ;; --ref) ref="${2:?missing ref}"; shift 2 ;; - --config) config_file="${2:?missing config path}"; shift 2 ;; --profile) profile="${2:?missing profile}"; profile_set=true; shift 2 ;; --languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;; --features) features="${2:?missing feature list}"; features_set=true; shift 2 ;; @@ -121,16 +118,7 @@ while [ "$#" -gt 0 ]; do esac done -if [ -n "$config_file" ]; then - [ -f "$config_file" ] || { printf 'Configuration file not found: %s\n' "$config_file" >&2; exit 1; } - mkdir -p .github - if [ "$(cd -- "$(dirname -- "$config_file")" && pwd)/$(basename -- "$config_file")" != "$(pwd)/.github/code-foundry.yml" ]; then - cp "$config_file" .github/code-foundry.yml - fi -fi - config_path=.github/code-foundry.yml -[ -f "$config_path" ] || config_path=.github/template.yml if [ -f "$config_path" ]; then config_value() { awk -F': ' -v key="$1" '$1 == key { value=$2; sub(/[[:space:]]+#.*/, "", value); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); print value; exit }' "$config_path" @@ -210,7 +198,7 @@ if [ -n "$license_file" ]; then sync_args+=(--license-file "$license_file"); fi if [ "$dry_run" = true ]; then sync_args+=(--check); else sync_args+=(--apply); fi if [ "$prune" = true ]; then sync_args+=(--prune); fi if [ "$force" = true ]; then sync_args+=(--force); fi -bash "$sync_script" "${sync_args[@]}" +REPO_FOUNDRY_INIT=true bash "$sync_script" "${sync_args[@]}" if [ "$dry_run" = true ]; then printf '%s\n' 'Dry run complete; no files were changed.' @@ -221,7 +209,6 @@ mkdir -p .github config_value() { awk -F': ' -v key="$1" '$1 == key { value=$2; sub(/[[:space:]]+#.*/, "", value); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); print value; exit }' .github/code-foundry.yml } -template_ref="$(config_value template 2>/dev/null || true)" profile="$(config_value profile 2>/dev/null || true)" languages="$(config_value languages 2>/dev/null || true)" features="$(config_value features 2>/dev/null || true)" @@ -233,9 +220,6 @@ npm_publish="$(config_value npm_publish 2>/dev/null || true)" license="$(config_value license 2>/dev/null || true)" { printf 'version: 1\n' - if [ -n "$template_ref" ]; then - printf 'template: %s\n' "$template_ref" - fi printf 'profile: %s\n' "$profile" printf 'languages: %s\n' "$languages" printf 'features: %s\n' "$features" @@ -263,11 +247,6 @@ license="$(config_value license 2>/dev/null || true)" printf 'turbo_remote: %s\n' "$turbo_remote" } > .github/code-foundry.yml -if [ "$config_path" = .github/template.yml ] && [ -f .github/template.yml ]; then - rm .github/template.yml - printf '%s\n' 'Migrated .github/template.yml to .github/code-foundry.yml.' -fi - if [ "$bootstrap" = false ]; then printf '%s\n' 'Bootstrap skipped.' exit 0 diff --git a/.github/scripts/profile.sh b/.github/scripts/profile.sh index 12e60a50..0ca6d279 100755 --- a/.github/scripts/profile.sh +++ b/.github/scripts/profile.sh @@ -3,8 +3,7 @@ set -euo pipefail # Resolve repository settings with this precedence: # explicit REPO_FOUNDRY_* values (CLI callers can export them), then -# .github/code-foundry.yml, then detected defaults. A legacy -# .github/template.yml is accepted during migration. +# .github/code-foundry.yml, then detected defaults. root="${REPO_FOUNDRY_ROOT:-$PWD}" command="detect" @@ -33,7 +32,6 @@ done cd "$root" template_file=.github/code-foundry.yml -[ -f "$template_file" ] || template_file=.github/template.yml config_value() { local key="$1" diff --git a/.github/scripts/security.sh b/.github/scripts/security.sh index d4840e2e..8b583b41 100755 --- a/.github/scripts/security.sh +++ b/.github/scripts/security.sh @@ -107,7 +107,6 @@ package_manager() { configured="$(bash .github/scripts/profile.sh get package_manager 2>/dev/null || true)" else config_file=.github/code-foundry.yml - [ -f "$config_file" ] || config_file=.github/template.yml if [ -f "$config_file" ]; then configured="$(awk -F': ' '/^package_manager:/ {print $2; exit}' "$config_file")" fi diff --git a/.github/scripts/sync-protection.sh b/.github/scripts/sync-protection.sh index fb32db99..59f7557e 100755 --- a/.github/scripts/sync-protection.sh +++ b/.github/scripts/sync-protection.sh @@ -8,7 +8,6 @@ configured_features="all" configured_languages="auto" config_file=.github/code-foundry.yml -[ -f "$config_file" ] || config_file=.github/template.yml if [ -f "$config_file" ]; then configured_features="$(awk -F': ' '/^features:/ {print $2; exit}' "$config_file")" configured_languages="$(awk -F': ' '/^languages:/ {print $2; exit}' "$config_file")" diff --git a/.github/scripts/sync-template.sh b/.github/scripts/sync-template.sh index feea6540..9789330d 100755 --- a/.github/scripts/sync-template.sh +++ b/.github/scripts/sync-template.sh @@ -13,7 +13,6 @@ Options: --languages LIST auto or comma-separated: typescript,rust,python,solidity --features LIST all or comma-separated standard features --package-manager NAME auto, bun, pnpm, yarn, or npm - --config PATH Use a .github/code-foundry.yml configuration file --runtime-repository OWNER/REPO Reusable workflow runtime repository --runtime-ref REF Reusable workflow runtime tag or branch --license NAME preserve, agpl-3.0-or-later, mit, or none @@ -28,7 +27,6 @@ EOF source_ref="main" mode="check" source="" -config_file="${REPO_FOUNDRY_CONFIG:-}" temp_dir="" profile="${REPO_FOUNDRY_PROFILE:-auto}" languages="${REPO_FOUNDRY_LANGUAGES:-auto}" @@ -51,7 +49,6 @@ runtime_repository_set=false [ -n "${REPO_FOUNDRY_RUNTIME_REPOSITORY:-}" ] && runtime_repository_set=true runtime_ref_set=false [ -n "${REPO_FOUNDRY_RUNTIME_REF:-}" ] && runtime_ref_set=true -template_ref="" release_type="${REPO_FOUNDRY_RELEASE_TYPE:-auto}" npm_publish="${REPO_FOUNDRY_NPM_PUBLISH:-false}" prune_standard="${REPO_FOUNDRY_PRUNE_STANDARD:-false}" @@ -127,7 +124,6 @@ while [ "$#" -gt 0 ]; do case "$1" in --source) source="${2:?missing source path or URL}"; shift 2 ;; --ref) source_ref="${2:?missing ref}"; shift 2 ;; - --config) config_file="${2:?missing config path}"; shift 2 ;; --profile) profile="${2:?missing profile}"; profile_set=true; shift 2 ;; --languages) languages="${2:?missing language list}"; languages_set=true; shift 2 ;; --features) features="${2:?missing feature list}"; features_set=true; shift 2 ;; @@ -146,15 +142,11 @@ while [ "$#" -gt 0 ]; do done [ -n "$source" ] || { usage >&2; exit 2; } -if [ -n "$config_file" ]; then - [ -f "$config_file" ] || { printf 'Configuration file not found: %s\n' "$config_file" >&2; exit 1; } - mkdir -p .github - if [ "$(cd -- "$(dirname -- "$config_file")" && pwd)/$(basename -- "$config_file")" != "$(pwd)/.github/code-foundry.yml" ]; then - cp "$config_file" .github/code-foundry.yml - fi -fi config_path=.github/code-foundry.yml -[ -f "$config_path" ] || config_path=.github/template.yml +[ -f "$config_path" ] || [ "${REPO_FOUNDRY_INIT:-false}" = true ] || { + printf '%s\n' 'Missing .github/code-foundry.yml; run `npx code-foundry init` first.' >&2 + exit 1 +} if [ -f "$config_path" ]; then config_value() { awk -F': ' -v key="$1" '$1 == key { value=$2; sub(/[[:space:]]+#.*/, "", value); gsub(/^[[:space:]]+|[[:space:]]+$/, "", value); print value; exit }' "$config_path" @@ -180,7 +172,6 @@ if [ -f "$config_path" ]; then if [ "$runtime_ref_set" = false ]; then runtime_ref="$(config_value runtime_ref)" fi - template_ref="$(config_value template)" if [ "$release_type_set" != true ]; then configured_release_type="$(config_value release_type)" [ -n "$configured_release_type" ] && release_type="$configured_release_type" @@ -280,7 +271,6 @@ fi # Prefer an explicit CLI/environment value, then the target's saved contract, # then the source template's contract, and finally the stable public runtime. source_config="$template_root/.github/code-foundry.yml" -[ -f "$source_config" ] || source_config="$template_root/.github/template.yml" if [ -z "$runtime_ref" ] && [ -f "$source_config" ]; then runtime_ref="$(awk -F': ' '/^runtime_ref:/ {print $2; exit}' "$source_config")" fi @@ -311,11 +301,6 @@ if [ -f "$template_root/.github/scripts/profile.sh" ]; then npm_publish="$(printf '%s\n' "$profile_output" | awk -F= '$1 == "npm_publish" {print substr($0, index($0, "=") + 1)}')" fi -if [ -f "$template_root/package.json" ]; then - template_version="$(awk -F'"' '/"version"[[:space:]]*:/ {print $4; exit}' "$template_root/package.json")" - [ -n "$template_version" ] && template_ref="code-foundry@$template_version" -fi - files=( .editorconfig .gitattributes @@ -472,7 +457,6 @@ done source_runtime_repository="" source_runtime_ref="" source_config="$template_root/.github/code-foundry.yml" -[ -f "$source_config" ] || source_config="$template_root/.github/template.yml" if [ -f "$source_config" ]; then source_runtime_repository="$(awk -F': ' '/^runtime_repository:/ {print $2; exit}' "$source_config")" source_runtime_ref="$(awk -F': ' '/^runtime_ref:/ {print $2; exit}' "$source_config")" @@ -671,9 +655,6 @@ if [ "$mode" = "apply" ]; then mkdir -p .github { printf 'version: 1\n' - if [ -n "$template_ref" ]; then - printf 'template: %s\n' "$template_ref" - fi printf 'profile: %s\n' "$profile" printf 'languages: %s\n' "$languages" printf 'features: %s\n' "$features" @@ -702,10 +683,6 @@ if [ "$mode" = "apply" ]; then printf 'license_file: %s\n' "$license_file" fi } > .github/code-foundry.yml - if [ "$config_path" = .github/template.yml ] && [ -f .github/template.yml ]; then - rm .github/template.yml - printf '%s\n' 'Migrated .github/template.yml to .github/code-foundry.yml.' - fi fi if [ "$prune" = true ]; then diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index 1862f2c4..297a6b8a 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -48,7 +48,6 @@ jobs: npm_publish="$(bash .github/scripts/profile.sh get npm_publish)" else config_file=.github/code-foundry.yml - [ -f "$config_file" ] || config_file=.github/template.yml release_type="$(awk -F': ' '/^release_type:/ {print $2; exit}' "$config_file" 2>/dev/null || true)" npm_publish="$(awk -F': ' '/^npm_publish:/ {print $2; exit}' "$config_file" 2>/dev/null || true)" fi diff --git a/README.md b/README.md index 06ee9ea2..b34bc70d 100644 --- a/README.md +++ b/README.md @@ -18,25 +18,20 @@ Run this from the root of a repository: npx code-foundry init ``` -Useful variants: +Initialization is repository-aware. It detects supported languages, package +manager, profile, release strategy, and required mise tools, then writes the +resolved choices to `.github/code-foundry.yml`. ```bash -# Choose languages and the package manager -npx code-foundry init --languages typescript,python --package-manager bun - -# Choose a license -npx code-foundry init --license mit - -# Preview without changing files -npx code-foundry init --dry-run +# Review or change the generated configuration, then render it +npx code-foundry sync ``` For an existing installation, use `npx code-foundry sync`. Run `npx code-foundry doctor` to inspect the resulting repository configuration. -If `.github/code-foundry.yml` already exists, initialization uses it automatically. -You can also supply a different file with `--config PATH`; after changing the -file, run `npx code-foundry sync` to render the selected configuration. +If `.github/code-foundry.yml` already exists, `init` uses it as the repository +contract. For normal updates, edit that file and run `npx code-foundry sync`. ## What it installs @@ -56,31 +51,15 @@ preserved and can coexist with the standard baseline. ## Configuration -Initialization is flag-driven. The most important options are: - -```text ---profile auto|application|monorepo|minimal ---languages auto|typescript,rust,python,solidity ---features all|ci,codeql,security,test,draft-pr,release-pr,release,dependabot ---package-manager auto|bun|pnpm|yarn|npm ---runtime-repository OWNER/REPO ---runtime-ref TAG_OR_BRANCH ---release-type auto|node|python|rust|simple|none ---license agpl-3.0-or-later|mit|preserve|none ---license-file PATH -``` - -The selected profile is saved in `.github/code-foundry.yml`. Explicit flags take -precedence over `REPO_FOUNDRY_*` environment/repository variables, which take -precedence over that file and automatic detection. See +The generated configuration is the one place to control the baseline. See [Configuration reference](docs/CONFIGURATION.md) for the visual configuration guide and [Initialization and synchronization](docs/INITIALIZATION.md) for -the safety and precedence rules. +the two-command workflow. New repositories default to AGPL-3.0-or-later. Synchronization preserves an existing license unless a replacement is explicitly selected. Authored documentation, application files, custom workflows, and existing `.mise.toml` -files are preserved by default; use `--force` or `--prune` only intentionally. +files are preserved by default. ## Workflow model diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index f444009f..5889a0d8 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1,26 +1,26 @@ # Configuration reference -`.github/code-foundry.yml` is the repository's single Code Foundry control plane. -Initialize from it with: +Code Foundry has one repository-owned control plane: `.github/code-foundry.yml`. ```bash -npx code-foundry init --config .github/code-foundry.yml +npx code-foundry init ``` -After initialization, edit the file and run `npx code-foundry sync` to render -the selected callers and refresh the local baseline. Existing authored files -and custom workflows remain protected. +Initialization detects the repository and writes a fully resolved configuration. +Edit that file directly, then run `npx code-foundry sync`. ## Configuration flow ```text -code-foundry.yml - | - +--> profile and language detection - +--> enabled standard workflow callers - +--> runtime repository and ref - +--> workflow runner selection - +--> release, license, cache, and coverage policy +repository manifests and source + | + v + .github/code-foundry.yml + | + +--> mise and environment setup + +--> standard workflow callers + +--> runtime repository and version + +--> release, license, cache, and coverage policy ``` ## Core settings @@ -28,60 +28,25 @@ code-foundry.yml | Key | Values | Purpose | | --- | --- | --- | | `profile` | `auto`, `application`, `monorepo`, `minimal` | Repository shape | -| `languages` | `auto` or comma-separated languages | Toolchain and CodeQL scope | -| `package_manager` | `auto`, `bun`, `pnpm`, `yarn`, `npm` | Locked JavaScript setup | -| `features` | `all` or comma-separated names | Standard callers to install | -| `prune_standard` | `true` or `false` | Remove disabled standard callers on sync | -| `runtime_repository` | `OWNER/REPO` or blank | Reusable workflow source | +| `languages` | detected list | TypeScript, Rust, Python, Solidity | +| `package_manager` | `bun`, `pnpm`, `yarn`, `npm`, `none` | JavaScript setup | +| `features` | `all` or a list | Standard workflow callers | +| `prune_standard` | `true` or `false` | Remove disabled standard callers | +| `runtime_repository` | `OWNER/REPO` | Reusable workflow source | | `runtime_ref` | tag or branch | Reusable workflow version | -| `release_type` | `auto`, `node`, `python`, `rust`, `simple`, `none` | Release Please strategy | +| `release_type` | `node`, `python`, `rust`, `simple`, `none` | Release strategy | | `npm_publish` | `true` or `false` | Opt into npm publication | -| `license` | license name, `preserve`, `none` | License generation policy | -| `license_file` | path | Optional exact license source | +| `license` | license name, `preserve`, `none` | License policy | +| `runner` fields | GitHub runner names | Per-workflow runner policy | -Supported languages are TypeScript, Rust, Python, and Solidity. Supported -features are `ci`, `codeql`, `security`, `test`, `draft-pr`, `release-pr`, -`release`, and `dependabot`. +Supported features are `ci`, `codeql`, `security`, `test`, `draft-pr`, +`release-pr`, `release`, and `dependabot`. -## Workflow runners +## Editing workflow -Each caller can select its default runner without editing workflow YAML: +`init` creates the file and renders the baseline. `sync` reads the file and +refreshes standard files from the configured runtime. Generated callers are +short and replaceable; custom workflows and project documentation are kept. -| Key | Default | Controls | -| --- | --- | --- | -| `runner` | `ubuntu-latest` | Shared fallback | -| `unit_runner` | `ubuntu-slim` | Unit tests | -| `ci_runner` | `ubuntu-latest` | Format, lint, type-check, build | -| `test_runner` | `ubuntu-latest` | Integration, E2E, smoke | -| `security_runner` | `ubuntu-slim` | Profile and dependency security jobs | -| `codeql_runner` | `ubuntu-latest` | CodeQL detection and analyzers | -| `pr_runner` | `ubuntu-slim` | Draft and Release PR automation | -| `release_runner` | `ubuntu-slim` | Release and package publication | - -Use the full runner for native toolchains, browsers, or measured dependency -workloads. Use `ubuntu-slim` for lightweight orchestration and small projects. - -## Performance and quality - -| Key | Default | Purpose | -| --- | --- | --- | -| `cache_packages` | `auto` | Package-store caching policy | -| `cache_build` | `auto` | Build-cache policy | -| `coverage_minimum` | `80` | Shared Bun/Python coverage target | -| `turbo_remote` | `auto` | Turborepo remote-cache policy | - -Turborepo remote caching still requires the repository secret `TURBO_TOKEN` and -the variable `TURBO_TEAM`; the file controls policy, not credentials. - -## Precedence - -The resolved value order is: - -```text -explicit CLI flag -> REPO_FOUNDRY_* variable -> code-foundry.yml -> detection/default -``` - -Use variables for temporary CI overrides. Keep durable repository policy in -`.github/code-foundry.yml` so humans and agents have one discoverable source of -truth. The example file is a commented starter guide; it is intentionally not -a second generated configuration. +The generated configuration includes all defaults so humans and agents can +understand the repository without memorizing flags or environment variables. diff --git a/docs/INITIALIZATION.md b/docs/INITIALIZATION.md index 6cb20fea..2e5775a6 100644 --- a/docs/INITIALIZATION.md +++ b/docs/INITIALIZATION.md @@ -1,67 +1,42 @@ # Initialization and synchronization -## Commands +## The two-command workflow -Run the package from a repository root: +Run these commands from a repository root: ```bash -npx code-foundry init [options] -npx code-foundry sync [options] +npx code-foundry init +npx code-foundry sync npx code-foundry doctor ``` -When `.github/code-foundry.yml` already exists, `init` treats it as the primary -configuration and does not replace its values with CLI defaults. To use another -file, pass it explicitly: +`init` detects supported languages, package manager, repository profile, +release strategy, mise tools, and standard features. It writes the resolved +choices to `.github/code-foundry.yml`, initializes the local environment, and +renders the standard baseline. -```bash -npx code-foundry init --config ./configs/code-foundry.yml -``` - -The file is copied to `.github/code-foundry.yml`, normalized, and used to render -the standard callers. Later edits can be applied with `npx code-foundry sync`. - -Use `--dry-run` to preview changes. Use `--no-bootstrap` when the repository is -being initialized in CI or on a machine without mise. Run -`bash .github/scripts/bootstrap.sh` later to install tools, enable hooks, and -run the repository doctor. - -## Profiles and features +After reviewing or editing the configuration, run `sync` to apply it. Sync can +be run at any time to pull in a newer runtime configured by `runtime_ref`. -Supported languages are `typescript`, `rust`, `python`, and `solidity`. -`--languages auto` detects them from manifests and source files. Profiles are -`auto`, `application`, `monorepo`, and `minimal`. +## Detection -Standard features are `ci`, `codeql`, `security`, `test`, `draft-pr`, -`release-pr`, `release`, and `dependabot`. `all` enables every standard -feature. `--prune` removes disabled standard workflows only; it never removes -custom workflows. +Supported languages are TypeScript, Rust, Python, and Solidity. Detection uses +manifests, lockfiles, source extensions, workspace metadata, and existing +project scripts. The generated values are explicit, so later syncs are stable +until a maintainer changes the file. ## Runtime selection -Reusable workflow callers require a literal repository and ref. By default, -the initializer derives the runtime repository from the source template. Use -these options for a fork or a staged runtime: - -```bash -npx code-foundry init \ - --runtime-repository OWNER/REPO \ - --runtime-ref v1.2.3 -``` - -Both values are persisted in `.github/code-foundry.yml` and rendered into the -standard callers. `REPO_FOUNDRY_RUNTIME_REPOSITORY` and -`REPO_FOUNDRY_RUNTIME_REF` provide equivalent environment/repository-variable -overrides. +Workflow callers use `runtime_repository` and `runtime_ref` from +`.github/code-foundry.yml`. Change those values directly for a fork or staged +runtime, then run sync. -## Safety rules +## Preservation rules -Synchronization preserves authored README and policy documents, existing -`.mise.toml` selections, application code, custom workflows, and repository -documentation. Existing licenses are preserved unless a license or license -file is explicitly selected. Use `--force` only when intentionally refreshing -protected standard documents. +Sync updates standard Code Foundry files only. It preserves application code, +authored documentation, existing `.mise.toml` selections, and custom workflows +such as deployment, search, Slither, or monitoring workflows. -The initializer generates `.github/code-foundry.yml` as the repository-owned -configuration contract. Keep project-specific settings there rather than -editing generated workflow callers by hand. +The environment bootstrap installs or reuses mise-managed tools, enables the +repository hooks, and runs the repository doctor. Re-run +`bash .github/scripts/bootstrap.sh` if local tools need to be repaired. diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md index 2cf4687c..c28236c9 100644 --- a/docs/WORKFLOWS.md +++ b/docs/WORKFLOWS.md @@ -58,7 +58,8 @@ Use the initializer's protection helper after reviewing the repository's enabled features: ```bash -bash .github/scripts/init-repo.sh --protection +Use the repository's GitHub settings or the maintainer's branch-protection +automation to apply required checks after initialization. ``` Keep strict status checks, linear history, and conversation resolution enabled. diff --git a/src/cli.mjs b/src/cli.mjs index 61a4cc1f..b89fcfd6 100644 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -10,31 +10,19 @@ const packageRoot = resolve(fileURLToPath(new URL('..', import.meta.url))) const usage = `code-foundry — initialize and maintain agent-ready repositories Usage: - code-foundry init [options] - code-foundry sync [options] - code-foundry doctor [--target PATH] + npx code-foundry init [--target PATH] + npx code-foundry sync [--target PATH] + npx code-foundry doctor [--target PATH] -Init/sync options: - --target PATH Repository directory (default: current directory) - --source PATH_OR_URL Template source override - --ref REF Template branch or tag (default: main) - --config PATH Use a .github/code-foundry.yml configuration file - --profile NAME auto, application, monorepo, or minimal - --languages LIST auto or typescript,rust,python,solidity - --features LIST all or ci,codeql,security,test,draft-pr,release-pr,release,dependabot - --package-manager NAME auto, bun, pnpm, yarn, or npm - --runtime-repository OWNER/REPO Reusable workflow runtime repository - --runtime-ref REF Reusable workflow runtime tag or branch - --release-type NAME auto, node, python, rust, simple, or none - --license NAME agpl-3.0-or-later, mit, preserve, or none - --license-file PATH Use an exact custom license file - --npm-publish Enable npm publication in the release workflow - --dry-run Preview changes without writing files - --force Replace protected standard docs/templates - --prune Remove disabled standard workflows - --protection Synchronize main branch protections (init only) - --no-bootstrap Skip mise/hooks/doctor bootstrap after init - -h, --help Show this help +The repository configuration lives in .github/code-foundry.yml. +init detects the repository, creates that file, and renders the baseline. +sync reads it and refreshes standard files from the configured runtime. + +Options: + --target PATH Repository directory (default: current directory) + --dry-run Preview changes without writing files + --force Replace protected standard documents + -h, --help Show this help ` function fail(message) { @@ -44,55 +32,7 @@ function fail(message) { function parseArgs(argv) { const command = argv[0] && !argv[0].startsWith('-') ? argv.shift() : 'init' - const options = { - target: process.cwd(), - source: packageRoot, - ref: 'main', - config: process.env.REPO_FOUNDRY_CONFIG || '', - profile: process.env.REPO_FOUNDRY_PROFILE || 'auto', - languages: process.env.REPO_FOUNDRY_LANGUAGES || 'auto', - features: process.env.REPO_FOUNDRY_FEATURES || 'all', - packageManager: process.env.REPO_FOUNDRY_PACKAGE_MANAGER || 'auto', - // Leave this empty unless the caller explicitly selects a runtime. The - // initializer can then derive the runtime from --source, which keeps - // organization forks plug-and-play. - runtimeRepository: process.env.REPO_FOUNDRY_RUNTIME_REPOSITORY || '', - runtimeRef: process.env.REPO_FOUNDRY_RUNTIME_REF || '', - releaseType: process.env.REPO_FOUNDRY_RELEASE_TYPE || 'auto', - license: process.env.REPO_FOUNDRY_LICENSE || (command === 'init' ? 'agpl-3.0-or-later' : 'preserve'), - licenseFile: process.env.REPO_FOUNDRY_LICENSE_FILE || '', - npmPublish: process.env.REPO_FOUNDRY_NPM_PUBLISH === 'true', - languagesSet: false, - featuresSet: false, - profileSet: Boolean(process.env.REPO_FOUNDRY_PROFILE), - packageManagerSet: Boolean(process.env.REPO_FOUNDRY_PACKAGE_MANAGER), - runtimeRepositorySet: Boolean(process.env.REPO_FOUNDRY_RUNTIME_REPOSITORY), - runtimeRefSet: Boolean(process.env.REPO_FOUNDRY_RUNTIME_REF), - releaseTypeSet: Boolean(process.env.REPO_FOUNDRY_RELEASE_TYPE), - licenseSet: Boolean(process.env.REPO_FOUNDRY_LICENSE), - licenseFileSet: Boolean(process.env.REPO_FOUNDRY_LICENSE_FILE), - npmPublishSet: Boolean(process.env.REPO_FOUNDRY_NPM_PUBLISH), - dryRun: false, - prune: false, - force: false, - protection: false, - bootstrap: true, - } - const values = new Map([ - ['--target', 'target'], - ['--source', 'source'], - ['--ref', 'ref'], - ['--config', 'config'], - ['--profile', 'profile'], - ['--languages', 'languages'], - ['--features', 'features'], - ['--package-manager', 'packageManager'], - ['--runtime-repository', 'runtimeRepository'], - ['--runtime-ref', 'runtimeRef'], - ['--release-type', 'releaseType'], - ['--license', 'license'], - ['--license-file', 'licenseFile'], - ]) + const options = { target: process.cwd(), dryRun: false, force: false } while (argv.length) { const arg = argv.shift() @@ -100,28 +40,13 @@ function parseArgs(argv) { console.log(usage) process.exit(0) } - if (values.has(arg)) { + if (arg === '--target') { const value = argv.shift() - if (!value || value.startsWith('-')) fail(`${arg} requires a value`) - options[values.get(arg)] = value - if (arg === '--languages') options.languagesSet = true - if (arg === '--features') options.featuresSet = true - if (arg === '--profile') options.profileSet = true - if (arg === '--package-manager') options.packageManagerSet = true - if (arg === '--runtime-repository') options.runtimeRepositorySet = true - if (arg === '--runtime-ref') options.runtimeRefSet = true - if (arg === '--release-type') options.releaseTypeSet = true - if (arg === '--license') options.licenseSet = true - if (arg === '--license-file') options.licenseFileSet = true - continue - } - if (arg === '--dry-run') options.dryRun = true + if (!value || value.startsWith('-')) fail('--target requires a path') + options.target = value + } else if (arg === '--dry-run') options.dryRun = true else if (arg === '--force') options.force = true - else if (arg === '--prune') options.prune = true - else if (arg === '--protection') options.protection = true - else if (arg === '--no-bootstrap') options.bootstrap = false - else if (arg === '--npm-publish') { options.npmPublish = true; options.npmPublishSet = true } - else fail(`unknown option: ${arg}`) + else fail(`unknown option: ${arg}; run --help for the supported options`) } return { command, options } @@ -142,51 +67,20 @@ function run(script, args, target) { function main() { const { command, options } = parseArgs(process.argv.slice(2)) const target = resolve(options.target) - const common = ['--source', options.source, '--ref', options.ref] - if (options.config) common.push('--config', options.config) - if (options.profileSet) common.push('--profile', options.profile) - if (options.languagesSet) common.push('--languages', options.languages) - if (options.featuresSet) common.push('--features', options.features) - if (options.packageManagerSet) common.push('--package-manager', options.packageManager) - if (options.runtimeRepositorySet) common.push('--runtime-repository', options.runtimeRepository) - if (options.runtimeRefSet) common.push('--runtime-ref', options.runtimeRef) - if (options.releaseTypeSet) common.push('--release-type', options.releaseType) - if (options.licenseSet) common.push('--license', options.license) - if (options.licenseFileSet && options.licenseFile) common.push('--license-file', options.licenseFile) - if (options.prune) common.push('--prune') - if (options.force) common.push('--force') + const common = ['--source', packageRoot, '--ref', 'main'] if (options.dryRun) common.push('--check') else common.push('--apply') + if (options.force) common.push('--force') if (command === 'init') { - const initArgs = [ - '--source', options.source, - '--ref', options.ref, - ] - if (options.profileSet) initArgs.push('--profile', options.profile) - if (options.languagesSet) initArgs.push('--languages', options.languages) - if (options.featuresSet) initArgs.push('--features', options.features) - if (options.packageManagerSet) initArgs.push('--package-manager', options.packageManager) - if (options.releaseTypeSet) initArgs.push('--release-type', options.releaseType) - if (options.licenseSet) initArgs.push('--license', options.license) - if (options.config) initArgs.push('--config', options.config) - if (options.runtimeRepositorySet) initArgs.push('--runtime-repository', options.runtimeRepository) - if (options.runtimeRefSet) initArgs.push('--runtime-ref', options.runtimeRef) - if (options.licenseFileSet && options.licenseFile) initArgs.push('--license-file', options.licenseFile) - if (options.protection) initArgs.push('--protection') + const initArgs = ['--source', packageRoot, '--ref', 'main'] if (options.dryRun) initArgs.push('--dry-run') - if (options.prune) initArgs.push('--prune') if (options.force) initArgs.push('--force') - if (options.npmPublishSet && options.npmPublish) initArgs.push('--npm-publish') - if (!options.bootstrap) initArgs.push('--no-bootstrap') run('init-repo.sh', initArgs, target) - } else if (command === 'sync') { - run('sync-template.sh', common, target) - } else if (command === 'doctor') { - run('doctor.sh', [], target) - } else { - fail(`unknown command: ${command}`) } + else if (command === 'sync') run('sync-template.sh', common, target) + else if (command === 'doctor') run('doctor.sh', [], target) + else fail(`unknown command: ${command}`) } main()