From 79fa07421323ef7019aa5d7fcab900f13c929490 Mon Sep 17 00:00:00 2001 From: John Judd Date: Fri, 3 Jul 2026 23:28:27 -0500 Subject: [PATCH 1/7] Initial open-source release (v0.1.0 baseline) Extract prenup from the ep-tools monorepo into a standalone open-source project at github.com/c2fo/prenup. - Rename Go module to github.com/c2fo/prenup (dropping the /v2 path segment; version history resets to v0.1.0). - Retarget the release version check at c2fo/prenup with a plain "v" tag prefix instead of the monorepo's "prenup/" prefix. - Rewrite all C2FO/ep-tools URLs, install commands, and JSON Schema $id to the new repo location. - Sanitize prenup.example.yaml to drop monorepo-specific tasks. - Add open-source scaffolding modeled on c2fo/releasegen: LICENSE.md, CODE_OF_CONDUCT.md, .golangci.yml, .testcoverage.yml, ISSUE_TEMPLATE and CI workflows (ci, codeql, go-test-coverage, golangci-lint, validate-changelog). The releasegen release workflow follows c2fo/vfs and runs the published ghcr.io/c2fo/releasegen:v1 image. - Add .releasegen.yaml so the validate and release workflows share one source of truth for changelog rules. - Reset CHANGELOG.md to a single v0.1.0 entry summarizing current capabilities; drop internal-monorepo history and the v2 rewrite plan. Co-authored-by: Cursor --- .github/ISSUE_TEMPLATE/bug_report.md | 27 ++ .github/ISSUE_TEMPLATE/custom.md | 8 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/workflows/ci.yml | 24 + .github/workflows/codeql.yml | 101 +++++ .github/workflows/go-test-coverage.yml | 31 ++ .github/workflows/golangci-lint.yml | 70 +++ .github/workflows/releasegen.yml | 65 +++ .github/workflows/validate-changelog.yml | 23 + .gitignore | 10 + .golangci.yml | 157 +++++++ .mockery.yaml | 10 + .releasegen.yaml | 13 + .testcoverage.yml | 28 ++ CHANGELOG.md | 41 ++ CODE_OF_CONDUCT.md | 128 ++++++ LICENSE.md | 21 + README.md | 230 +++++++++- assets/prenup.schema.json | 68 +++ cmd/prenup/e2e_test.go | 285 ++++++++++++ cmd/prenup/main.go | 16 + docs/FUTURE.md | 165 +++++++ docs/PRD.md | 330 ++++++++++++++ docs/SCHEMA.md | 181 ++++++++ go.mod | 43 ++ go.sum | 82 ++++ internal/cli/config_cmd.go | 69 +++ internal/cli/init.go | 130 ++++++ internal/cli/install.go | 191 ++++++++ internal/cli/migrate.go | 69 +++ internal/cli/plan.go | 155 +++++++ internal/cli/root.go | 84 ++++ internal/cli/run.go | 409 +++++++++++++++++ internal/cli/run_internal_test.go | 189 ++++++++ internal/cli/version.go | 40 ++ internal/cli/versioncheck_test.go | 29 ++ internal/config/config.go | 300 ++++++++++++ internal/config/config_test.go | 147 ++++++ internal/config/migrate.go | 76 ++++ internal/config/migrate_test.go | 56 +++ internal/config/schema.go | 12 + internal/config/schema.json | 68 +++ internal/config/schema_test.go | 25 + internal/discover/changes.go | 98 ++++ internal/discover/discover_test.go | 54 +++ internal/discover/modules.go | 57 +++ internal/git/git.go | 195 ++++++++ internal/git/git_test.go | 181 ++++++++ internal/git/stash.go | 94 ++++ internal/hook/install.go | 142 ++++++ internal/hook/install_test.go | 130 ++++++ internal/lock/flock_unix.go | 19 + internal/lock/lock.go | 150 ++++++ internal/lock/lock_test.go | 113 +++++ internal/runner/events.go | 114 +++++ internal/runner/exec.go | 115 +++++ internal/runner/mocks/mocks.go | 225 +++++++++ internal/runner/multisink.go | 31 ++ internal/runner/multisink_test.go | 54 +++ internal/runner/runner.go | 502 +++++++++++++++++++++ internal/runner/runner_test.go | 264 +++++++++++ internal/runner/stage.go | 74 +++ internal/runner/stage_test.go | 114 +++++ internal/runner/template.go | 50 ++ internal/ui/agent.go | 72 +++ internal/ui/human/runner_ui.go | 331 ++++++++++++++ internal/ui/human/selection.go | 187 ++++++++ internal/ui/human/summary.go | 115 +++++ internal/ui/jsonout/jsonout.go | 72 +++ internal/ui/jsonout/jsonout_test.go | 126 ++++++ internal/ui/markdown/markdown.go | 228 ++++++++++ internal/ui/markdown/markdown_test.go | 207 +++++++++ internal/ui/output.go | 37 ++ internal/versioncheck/versioncheck.go | 160 +++++++ internal/versioncheck/versioncheck_test.go | 295 ++++++++++++ prenup.example.yaml | 61 +++ prenup.png | Bin 0 -> 273730 bytes 77 files changed, 8892 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/custom.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/go-test-coverage.yml create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .github/workflows/releasegen.yml create mode 100644 .github/workflows/validate-changelog.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 .mockery.yaml create mode 100644 .releasegen.yaml create mode 100644 .testcoverage.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 LICENSE.md create mode 100644 assets/prenup.schema.json create mode 100644 cmd/prenup/e2e_test.go create mode 100644 cmd/prenup/main.go create mode 100644 docs/FUTURE.md create mode 100644 docs/PRD.md create mode 100644 docs/SCHEMA.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cli/config_cmd.go create mode 100644 internal/cli/init.go create mode 100644 internal/cli/install.go create mode 100644 internal/cli/migrate.go create mode 100644 internal/cli/plan.go create mode 100644 internal/cli/root.go create mode 100644 internal/cli/run.go create mode 100644 internal/cli/run_internal_test.go create mode 100644 internal/cli/version.go create mode 100644 internal/cli/versioncheck_test.go create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go create mode 100644 internal/config/migrate.go create mode 100644 internal/config/migrate_test.go create mode 100644 internal/config/schema.go create mode 100644 internal/config/schema.json create mode 100644 internal/config/schema_test.go create mode 100644 internal/discover/changes.go create mode 100644 internal/discover/discover_test.go create mode 100644 internal/discover/modules.go create mode 100644 internal/git/git.go create mode 100644 internal/git/git_test.go create mode 100644 internal/git/stash.go create mode 100644 internal/hook/install.go create mode 100644 internal/hook/install_test.go create mode 100644 internal/lock/flock_unix.go create mode 100644 internal/lock/lock.go create mode 100644 internal/lock/lock_test.go create mode 100644 internal/runner/events.go create mode 100644 internal/runner/exec.go create mode 100644 internal/runner/mocks/mocks.go create mode 100644 internal/runner/multisink.go create mode 100644 internal/runner/multisink_test.go create mode 100644 internal/runner/runner.go create mode 100644 internal/runner/runner_test.go create mode 100644 internal/runner/stage.go create mode 100644 internal/runner/stage_test.go create mode 100644 internal/runner/template.go create mode 100644 internal/ui/agent.go create mode 100644 internal/ui/human/runner_ui.go create mode 100644 internal/ui/human/selection.go create mode 100644 internal/ui/human/summary.go create mode 100644 internal/ui/jsonout/jsonout.go create mode 100644 internal/ui/jsonout/jsonout_test.go create mode 100644 internal/ui/markdown/markdown.go create mode 100644 internal/ui/markdown/markdown_test.go create mode 100644 internal/ui/output.go create mode 100644 internal/versioncheck/versioncheck.go create mode 100644 internal/versioncheck/versioncheck_test.go create mode 100644 prenup.example.yaml create mode 100644 prenup.png diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..891c617 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 0000000..b894315 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,8 @@ +--- +name: Custom issue template +about: Describe this issue template's purpose here. +title: '' +labels: '' +assignees: '' + +--- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0a686e7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: Golang Test +on: + pull_request: + branches: + - main +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + env: + GO111MODULE: on + steps: + - name: Setup Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 #v6 + with: + go-version: '1.26' + + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6 + + - name: Run tests + run: go test ./... -v + env: + GOFLAGS: -mod=readonly diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..fc5feb5 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,101 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + pull_request: + branches: [ "main" ] + schedule: + - cron: '17 19 * * 0' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + env: + GOPROXY: proxy.golang.org,direct + + strategy: + fail-fast: false + matrix: + include: + - language: go + build-mode: autobuild + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6 + - name: Setup Go + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 #v6 + with: + go-version: '1.26' # The Go version to download (if necessary) and use. + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 #v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 #v4 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/go-test-coverage.yml b/.github/workflows/go-test-coverage.yml new file mode 100644 index 0000000..dc4cda8 --- /dev/null +++ b/.github/workflows/go-test-coverage.yml @@ -0,0 +1,31 @@ +name: Go Test Coverage +on: + pull_request: + branches: + - main +jobs: + check-coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Set up Go + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + with: + go-version: '1.26' + check-latest: true + + - name: Generate test coverage + run: | + go test \ + -race \ + -covermode=atomic \ + -coverpkg=./internal/...,./cmd/... \ + -coverprofile=coverage.out \ + ./... + + - name: Check test coverage + uses: vladopajic/go-test-coverage@679e6807f68f2440a4c43d386442a1d0041838a9 # v2.18.3 + with: + config: ./.testcoverage.yml diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..35864f2 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,70 @@ +name: golangci-lint +on: + pull_request: + branches: + - main + +permissions: + # Required: allow read access to the content for analysis. + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read + # Optional: allow write access to checks to allow the action to annotate code in the PR. + checks: write + +jobs: + detect-modules: + name: detect-modules + runs-on: ubuntu-latest + outputs: + modules: ${{ steps.set-modules.outputs.modules }} + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - id: set-modules + run: | + # Find all directories containing go.mod files + modules=$(find . -name "go.mod" -type f | sed 's|/go.mod$||' | sort) + + # Convert to JSON array - ensure proper JSON format + echo "$modules" | jq -R . | jq -s -c . > modules.json + + echo "Found modules:" + cat modules.json | jq -r '.[]' + + # Set output with compact JSON + echo "modules=$(cat modules.json)" >> $GITHUB_OUTPUT + + golangci: + name: golangci-lint + runs-on: ubuntu-latest + needs: detect-modules + strategy: + matrix: + module: ${{ fromJSON(needs.detect-modules.outputs.modules) }} + steps: + - name: Setup Go + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + with: + go-version: '1.26' + check-latest: true + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: golangci-lint + uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: latest + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + args: --config=${{ github.workspace }}/.golangci.yml --timeout=10m + + # Optional: show only new issues if it's a pull request. The default value is `false`. + only-new-issues: true + + # Optional: if set to true then the action will use pre-installed Go. + # skip-go-installation: true + working-directory: ${{ matrix.module }} diff --git a/.github/workflows/releasegen.yml b/.github/workflows/releasegen.yml new file mode 100644 index 0000000..57318e3 --- /dev/null +++ b/.github/workflows/releasegen.yml @@ -0,0 +1,65 @@ +name: Release by Changelog + +on: + push: + branches: + - main + - v6 + workflow_dispatch: + inputs: + branch: + description: 'Branch to create a release from' + required: true + default: 'main' + version: + description: 'Specify the semantic version for the release (vX.Y.Z)' + required: true + reason: + description: 'Reason for the manual release' + required: false + +permissions: + contents: write + +jobs: + release: + name: ReleaseGen + runs-on: ubuntu-latest + + steps: + - name: Generate GitHub App token + id: generate-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 #v2.2.2 + with: + app-id: ${{ secrets.RELEASEGEN_APP_ID }} + private-key: ${{ secrets.RELEASEGEN_APP_PRIVATE_KEY }} + + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.inputs.branch || github.ref_name }} + fetch-depth: 0 + token: ${{ steps.generate-token.outputs.token }} + + - name: Run ReleaseGen + # custom_change_types now live in .releasegen.yaml so the validate + # and release workflows share one source of truth. Per-run values + # stay in env below. + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_REF_NAME: ${{ github.event.inputs.branch || github.ref_name }} + MANUAL_VERSION: ${{ github.event.inputs.version || '' }} + REASON: ${{ github.event.inputs.reason || '' }} + run: | + docker run --rm \ + -e GITHUB_TOKEN \ + -e GITHUB_REPOSITORY \ + -e GITHUB_ACTOR \ + -e GITHUB_REF_NAME \ + -e MANUAL_VERSION \ + -e REASON \ + -v "$(pwd):/workspace" \ + ghcr.io/c2fo/releasegen:v1 \ + --repo-root /workspace diff --git a/.github/workflows/validate-changelog.yml b/.github/workflows/validate-changelog.yml new file mode 100644 index 0000000..105723b --- /dev/null +++ b/.github/workflows/validate-changelog.yml @@ -0,0 +1,23 @@ +name: Validate Changelog + +on: + pull_request: + +permissions: + contents: read + +jobs: + validate-changelog: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + with: + fetch-depth: 0 + + - name: Validate changelog + run: | + docker run --rm \ + -v "$(pwd):/workspace" \ + ghcr.io/c2fo/releasegen:v1 \ + validate --repo-root /workspace diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..073b1ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.idea/ +.vscode/ + +# Build artifacts. +/prenup +/cmd/prenup/prenup + +# Test coverage output. +coverage.out diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..45856aa --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,157 @@ +version: "2" +run: + go: "1.26" + issues-exit-code: 1 + tests: true + allow-parallel-runners: false +output: + formats: + text: + path: stdout + print-linter-name: true + print-issued-lines: true + path-prefix: "" +linters: + default: none + enable: + - bodyclose + - errcheck + - errname + - errorlint + - gocritic + - gocyclo + - gosec + - govet + - ineffassign + - intrange + - lll + - misspell + - nolintlint + - perfsprint + - prealloc + - revive + - staticcheck + - tagliatelle + - testifylint + - unconvert + - unparam + - unused + - usetesting + - wastedassign + - whitespace + settings: + errcheck: + check-type-assertions: false + check-blank: false + gocritic: + disabled-checks: + - dupImport + - ifElseChain + - octalLiteral + - whyNoLint + - wrapperFunc + - hugeParam + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + settings: + captLocal: + paramsOnly: true + elseif: + skipBalanced: true + nestingReduce: + bodyWidth: 5 + rangeExprCopy: + sizeThreshold: 512 + skipTestFuncs: true + rangeValCopy: + sizeThreshold: 128 + skipTestFuncs: true + truncateCmp: + skipArchDependent: true + underef: + skipRecvDeref: true + unnamedResult: + checkExported: true + gocyclo: + min-complexity: 15 + gosec: + excludes: + - G115 # Integer overflow conversion - too many false positives + - G117 # Exported field names matching secret patterns (false positives for Options structs) + - G101 # Hardcoded credentials detection (false positives in test files with example data) + - G703 # Path traversal taint analysis (false positives for legitimate file operations) + - G705 # XSS taint analysis (false positives for stdout/stderr output) + lll: + line-length: 140 + tab-width: 1 + misspell: + locale: US + nolintlint: + require-explanation: false + require-specific: false + allow-unused: false + revive: + severity: warning + staticcheck: + checks: + - -ST1000 + - -ST1003 + - -ST1016 + - -ST1020 + - -ST1021 + - -ST1022 + - all + tagliatelle: + # Prenup's NDJSON runner events (see internal/ui/jsonout, docs/SCHEMA.md) + # and the `.prenup.yaml` user config (see internal/config) are stable, + # snake_case contracts consumed by users and agent tooling. + case: + rules: + json: snake + yaml: snake + unparam: + check-exported: false + exclusions: + generated: lax + rules: + - linters: + - mnd + path: _test\.go + - linters: + - lll + source: '^//go:generate ' + paths: + - docs + - mocks +issues: + max-issues-per-linter: 50 + max-same-issues: 3 + uniq-by-line: true + new: false +severity: + default: error +formatters: + enable: + - gofmt + - goimports + settings: + gci: + sections: + - standard + - default + - prefix(github.com/c2fo/) + custom-order: false + gofmt: + simplify: true + goimports: + local-prefixes: + - github.com/c2fo/ + exclusions: + generated: lax + paths: + - docs + - mocks diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..7862857 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,10 @@ +dir: "{{.InterfaceDir}}/mocks" +filename: "mocks.go" +pkgname: "mocks" +structname: "{{.InterfaceName}}" +template: testify +packages: + github.com/c2fo/prenup/internal/runner: + interfaces: + Executor: + Sink: diff --git a/.releasegen.yaml b/.releasegen.yaml new file mode 100644 index 0000000..da4268f --- /dev/null +++ b/.releasegen.yaml @@ -0,0 +1,13 @@ +# ReleaseGen configuration for prenup. +# +# Per-repo, rarely-changing options live here so the validate workflow and the +# release workflow read them from a single source. Per-invocation values +# (GITHUB_TOKEN, MANUAL_VERSION, etc.) stay in the workflow env / CLI flags. + +custom_change_types: + Documentation: patch + +validate: + # Require every PR that touches non-CHANGELOG files to also add at least + # one entry under [Unreleased]. + require_changelog_entry: true diff --git a/.testcoverage.yml b/.testcoverage.yml new file mode 100644 index 0000000..0ff9ab8 --- /dev/null +++ b/.testcoverage.yml @@ -0,0 +1,28 @@ +# Configuration for vladopajic/go-test-coverage. +# See https://github.com/vladopajic/go-test-coverage for full schema. + +# Path to coverage profile file produced by `go test -coverprofile`. +profile: coverage.out + +# Coverage thresholds (percentages, 0-100). Starting conservative for v0.1.0; +# raise as coverage improves, never lower. +threshold: + total: 40 + package: 0 + file: 0 + +# Exclude generated and non-testable code from coverage statistics. +exclude: + paths: + - mocks/ + # Interactive Bubble Tea TUI; exercised end-to-end via cmd/prenup/e2e_test.go. + - internal/ui/human/ + # Trivial output-mode selection shim. + - internal/ui/output.go + # Main entrypoint (single delegation call). + - cmd/prenup/main.go + +breakdown-file-name: '' + +diff: + base-breakdown-file-name: '' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5c73be1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2026-07-03 + +### Added + +- Initial open-source release. +- Interactive, configuration-driven Git pre-commit hook runner. +- Subcommands: `run`, `plan`, `install`, `uninstall`, `init`, `migrate`, + `config validate`, `config schema`, `version`. +- Per-task path filtering with doublestar globs (`paths`, `paths_ignore`, + `exclude`). +- Per-module change discovery via configurable `module_markers`, with + bounded per-module concurrency for `per_module` tasks. +- Stash-and-restore of unstaged changes (`clean_worktree`) so tasks see + exactly what will be committed. +- OS-level advisory lock on `.git/prenup.lock` to prevent concurrent + `prenup run` invocations from racing on the worktree. +- Output modes: interactive Bubble Tea UI (`human`), streaming markdown + digest (`markdown`), and NDJSON event stream (`json`) with a leading + `agent_hint` bootstrap line for LLM/agent consumers. +- Automatic staging of newly-generated files matching `output_patterns` + for tasks that declare `stage_output: true`. +- Graceful cancellation: SIGTERM with grace period on Ctrl-C or parent + timeout, then SIGKILL if the task does not exit. +- Template variables in `command` and `working_dir`: `{{.repo_root}}`, + `{{.module_root}}`, `{{.module_path}}`, `{{.module_name}}`. +- `prenup migrate` to convert v1 `.prenup.yml` files to the current v2 + schema. +- Embedded JSON Schema for editor `$schema` integration, published at + `assets/prenup.schema.json`. +- GitHub Releases API version check with a one-line update notice; honors + `PRENUP_GITHUB_TOKEN`, `GITHUB_TOKEN`, or `GH_TOKEN` for authenticated + requests. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..24632a0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +john.judd@c2fo.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d612f23 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2026 C2FO, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 07fd7da..e8631eb 100644 --- a/README.md +++ b/README.md @@ -1 +1,229 @@ -# prenup \ No newline at end of file +![prenup.png](prenup.png) + +# Prenup — Interactive Pre-Commit Hook Utility + +Prenup runs user-defined tasks (tests, linters, doc generators, custom scripts) +as a Git pre-commit hook. It is designed to be fast, selective, and visible: +only the modules that actually changed get checked, task output streams live, +and the developer stays in the loop through a simple interactive UI. + +Prenup provides dedicated subcommands (`install`, `init`, `run`, `plan`, +`migrate`), per-task path filtering, per-module parallelism, stash-and-restore +for safer checks, and agent-friendly output modes (markdown digest when piped, +NDJSON with `--output json`). + +## Quick start + +```bash +# Install the binary. +go install github.com/c2fo/prenup/cmd/prenup@latest + +# Inside your repo: +prenup init # scaffold .prenup.yaml +prenup install # write .git/hooks/pre-commit +``` + +You commit. Prenup runs. Pass or fail, you see why. + +## Subcommands + +| Command | Purpose | +|---|---| +| `prenup` | Default: run as the Git pre-commit hook. Equivalent to `prenup run`. | +| `prenup run [flags]` | Run the pre-commit checks without committing. | +| `prenup plan` | Print the plan for the current change set without executing. | +| `prenup install` | Install `.git/hooks/pre-commit`. Prompts on conflicts. | +| `prenup uninstall` | Remove the managed hook, restoring backups when present. | +| `prenup init` | Scaffold a starter `.prenup.yaml` from repo inspection. | +| `prenup migrate` | Convert a v1 `.prenup.yml` into a v2 `.prenup.yaml`. | +| `prenup config validate [path]` | Validate a config against the v2 schema. | +| `prenup config schema` | Print the embedded JSON schema. | +| `prenup version` | Print the binary version. | + +### `prenup run` flags + +- `--config PATH` — explicit config path. +- `--output MODE` — `auto`, `human`, `markdown`, or `json`. Default `auto`. +- `--task NAME` — run only the named task(s); repeatable, non-interactive. +- `--all` — ignore change detection; run against the full repo scope. +- `--no-interactive` — skip the selection UI; run all `default_selected` tasks. +- `--no-clean-worktree` — disable stash-and-restore. +- `--parallelism N` — cap per-module fan-out (0 = `NumCPU`). +- `--dry-run` — report what would run without executing. + +### `prenup install` conflict handling + +If a `pre-commit` hook already exists, `install` prompts for one of: + +- **replace** — back up the existing hook and write the prenup hook. +- **chain** — move the existing hook to `pre-commit.local`; the prenup hook + runs it first on every commit. +- **force** — overwrite without a backup. +- **abort** — leave everything alone. + +Non-interactive shells can pass `--force`, `--replace`, `--chain`, or +`--non-interactive` directly. When stdin is not a TTY, `install` refuses to +prompt and surfaces the conflict so CI scripts fail loudly instead of hanging. + +Use `--use-path` to record `prenup` (resolved via `$PATH` at commit time) +inside the hook script instead of the absolute path of the binary that ran +`install`. This is the right choice for shared dotfiles or any setup where +the binary may live in different locations across machines. + +## Output modes + +- **human** — full-screen Bubble Tea UI with a live task checklist and a + scrolling output viewport. Selected automatically when stdout is a TTY. +- **markdown** — plain streaming output during execution, followed by a + structured markdown digest summarizing each task. Selected automatically + when stdout is piped, when `NO_COLOR` is set, or when `TERM=dumb`. This is + the default for CI logs and for LLM-readable post-run summaries. +- **json** — one JSON object per line (NDJSON). Stable schema, safe for + agents and tooling. Always explicit: `--output json`. + +Both `markdown` and `json` modes lead with a self-describing preamble so +an AI agent (or operator) that has never heard of prenup can identify the +tool, find docs, and recognize hook-attributed failures from the very +first line of output. In `markdown` it is a `[prenup] â€Ļ` block; in `json` +it is a single `agent_hint` event before the runner stream begins. On +failure, the markdown digest's "Next steps" block also disambiguates +prenup-vs-git attribution and documents the `--no-verify` bypass. + +See [docs/SCHEMA.md](docs/SCHEMA.md) for the JSON event schema (including +the `agent_hint` bootstrap line). + +## Configuration + +Configuration lives at the repository root as `.prenup.yaml` (or `.prenup.yml`). + +```yaml +version: 2 +module_markers: + - go.mod +exclude: + - ".github/**" + - "**/*.yaml" +clean_worktree: true +max_parallelism: 0 +output: auto + +tasks: + - name: "Run tests" + default_selected: true + command: "go test ./..." + per_module: true + paths: + - "**/*.go" + paths_ignore: + - "**/*_mock.go" + + - name: "Generate CLI docs" + default_selected: true + per_module: false + command: "make docs" + output_patterns: + - "doc/cmd/**/*.md" + stage_output: true +``` + +See [prenup.example.yaml](prenup.example.yaml) for a larger example and +[docs/SCHEMA.md](docs/SCHEMA.md) for the full field reference. + +`prenup config validate` (and `prenup run` itself) checks structural fields +and the syntax of every doublestar pattern in `exclude`, `paths`, +`paths_ignore`, and `output_patterns`. Invalid patterns are reported with +the offending value and field path so they can be fixed before they silently +fail to match anything at runtime. + +### Template variables + +Available inside `command` and `working_dir`: + +| Variable | Description | +|---|---| +| `{{.repo_root}}` | Absolute path to the repo root. | +| `{{.module_root}}` | Absolute path to the current module. | +| `{{.module_path}}` | Module path relative to the repo root. | +| `{{.module_name}}` | Basename of the module directory. | + +## Behavior + +1. **Change discovery** — staged, unstaged, and untracked files (filtered by + repo-level `exclude` patterns). +2. **Module detection** — the nearest ancestor directory of each changed file + that contains any `module_markers` file. +3. **Task planning** — tasks filter their relevant files via `paths` / + `paths_ignore` (doublestar globs). `per_module: true` tasks fan out across + the resulting module set. +4. **Selection** — in human mode, an interactive checklist. In other modes, + `default_selected` tasks run automatically; `--task` overrides. +5. **Concurrency lock** — before any tasks run, prenup takes an OS-level + advisory lock on `.git/prenup.lock`. A second concurrent `prenup run` + against the same repo (e.g. a Git GUI that double-fires `git commit`) + exits non-zero with a clear "another prenup run is already in progress" + message instead of racing on the worktree. The lock auto-releases on + process exit, including crashes. `--dry-run` skips the lock. +6. **Stash-and-restore** — when `clean_worktree: true`, unstaged changes are + stashed with `--keep-index --include-untracked` before tasks run, and + restored afterward. +7. **Execution** — tasks run sequentially; a per-module task fans out across + modules concurrently (bounded by `max_parallelism`). The first failure + within a task skips remaining modules for that task and continues to the + next task. +8. **Output staging** — tasks that declare `stage_output: true` have + newly-created files matching `output_patterns` automatically `git add`-ed. + The "before" snapshot is taken immediately before each such task runs, so + one task cannot accidentally stage files generated by an earlier task in + the same run. For `per_module` tasks, staging is further scoped to the + task's modules: a task running in module `a/` will not auto-stage files + produced under module `b/`. +9. **Cancellation** — when prenup is interrupted (Ctrl-C, parent timeout, + etc.) the running task receives `SIGTERM` and is given a short grace + period to flush output and clean up before being forcefully killed. +10. **Exit** — non-zero if any task failed; zero otherwise. + +## Version check + +Each invocation queries the GitHub Releases API for the latest `v*` tag +with a short timeout. A newer version triggers a one-line update notice. +Failures are silent and never block a commit. + +For private repositories or to avoid unauthenticated rate limits, set one +of `PRENUP_GITHUB_TOKEN`, `GITHUB_TOKEN`, or `GH_TOKEN`. + +## Migrating from v1 + +Run `prenup migrate` in the root of a repo with a v1 `.prenup.yml`: + +```bash +prenup migrate # writes .prenup.yaml beside .prenup.yml +prenup migrate --in path --out path # explicit locations +``` + +Field names are preserved. New v2 features (`paths`, `paths_ignore`, +`module_markers`, `parallel`, `env`, `clean_worktree`) are not invented for +you; add them manually where useful. `prenup config validate` confirms the +migrated file parses cleanly. + +## Documentation + +- [docs/PRD.md](docs/PRD.md) — product requirements and behavior spec. +- [docs/SCHEMA.md](docs/SCHEMA.md) — config schema and JSON event stream. +- [docs/FUTURE.md](docs/FUTURE.md) — deferred improvements. +- [CHANGELOG.md](CHANGELOG.md) — release history. + +## Platform support + +Prenup targets Linux and macOS. Windows support is deliberately out of scope; +see [docs/FUTURE.md](docs/FUTURE.md). + +## Contributing + +Issues and pull requests are welcome. See the [Contributor Covenant Code of +Conduct](CODE_OF_CONDUCT.md). All PRs must update the `[Unreleased]` section +of [CHANGELOG.md](CHANGELOG.md); version numbers are assigned automatically at +release time. + +## License + +MIT. See [LICENSE.md](LICENSE.md). diff --git a/assets/prenup.schema.json b/assets/prenup.schema.json new file mode 100644 index 0000000..31ef3a1 --- /dev/null +++ b/assets/prenup.schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/c2fo/prenup/main/assets/prenup.schema.json", + "title": "Prenup v2 configuration", + "$comment": "This file is the canonical source. assets/prenup.schema.json must stay byte-identical. When adding a v3, replace `version.const: 2` with a `oneOf` over per-version schemas.", + "type": "object", + "required": ["version", "tasks"], + "additionalProperties": false, + "properties": { + "version": { + "type": "integer", + "const": 2, + "description": "Config schema version. Must be 2." + }, + "module_markers": { + "type": "array", + "items": { "type": "string" }, + "description": "File names whose presence marks a module directory. Defaults to ['go.mod']." + }, + "exclude": { + "type": "array", + "items": { "type": "string" }, + "description": "Doublestar glob patterns of files to ignore for change detection." + }, + "clean_worktree": { + "type": "boolean", + "description": "Stash unstaged changes around task execution so checks see exactly the pending commit. Default true." + }, + "max_parallelism": { + "type": "integer", + "minimum": 0, + "description": "Max concurrent per-module task runs. 0 means NumCPU." + }, + "output": { + "type": "string", + "enum": ["auto", "human", "markdown", "json"], + "description": "Default output mode. Overridable via --output." + }, + "tasks": { + "type": "array", + "items": { "$ref": "#/$defs/task" } + } + }, + "$defs": { + "task": { + "type": "object", + "required": ["name", "command"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "minLength": 1 }, + "default_selected": { "type": "boolean" }, + "command": { "type": "string", "minLength": 1 }, + "per_module": { "type": "boolean" }, + "working_dir": { "type": "string" }, + "paths": { "type": "array", "items": { "type": "string" } }, + "paths_ignore": { "type": "array", "items": { "type": "string" } }, + "output_patterns": { "type": "array", "items": { "type": "string" } }, + "stage_output": { "type": "boolean" }, + "parallel": { "type": "boolean" }, + "clean_worktree": { "type": "boolean" }, + "env": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } + } + } +} diff --git a/cmd/prenup/e2e_test.go b/cmd/prenup/e2e_test.go new file mode 100644 index 0000000..122ea99 --- /dev/null +++ b/cmd/prenup/e2e_test.go @@ -0,0 +1,285 @@ +package main_test + +import ( + "bytes" + "encoding/json" + "errors" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// buildBinary compiles the prenup binary into a temporary location and +// returns the path. The compiled binary is reused across subtests. +func buildBinary(t *testing.T) string { + t.Helper() + dir := t.TempDir() + out := filepath.Join(dir, "prenup") + // Build with the host go toolchain; the binary path is constructed from + // the test's TempDir and the go binary name is fixed. + cmd := exec.Command("go", "build", "-o", out, ".") //nolint:gosec // G204: fixed binary, controlled args. + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Run()) + return out +} + +// initRepo creates a fresh git repo rooted at dir, committing the passed +// files on top of an empty initial commit. +func initRepo(t *testing.T, dir string) { + t.Helper() + mustRunGit(t, dir, "init", "-b", "main") + mustRunGit(t, dir, "config", "user.email", "t@example.com") + mustRunGit(t, dir, "config", "user.name", "t") + mustRunGit(t, dir, "config", "commit.gpgsign", "false") + require.NoError(t, os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi\n"), 0o600)) + mustRunGit(t, dir, "add", "README.md") + mustRunGit(t, dir, "commit", "-m", "init") +} + +// mustRunGit runs `git ` in dir, failing the test on error. The git +// binary is fixed; only its arguments vary. +func mustRunGit(t *testing.T, dir string, args ...string) { + t.Helper() + cmd := exec.Command("git", args...) //nolint:gosec // G204: git binary is fixed in tests. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + require.NoErrorf(t, err, "git %v: %s", args, string(out)) +} + +// runPrenup executes the built binary in dir and returns stdout + exit code. +func runPrenup(t *testing.T, binary, dir string, args ...string) (string, int) { + t.Helper() + // binary is the path returned by buildBinary in this test process. + cmd := exec.Command(binary, args...) //nolint:gosec // G204: binary is built locally for the test. + cmd.Dir = dir + // Force non-interactive output for deterministic test behavior. + cmd.Env = append(os.Environ(), "NO_COLOR=1", "TERM=dumb") + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + exit := 0 + if err != nil { + ee := &exec.ExitError{} + if errors.As(err, &ee) { + exit = ee.ExitCode() + } else { + t.Fatalf("running prenup: %v\nstderr: %s", err, stderr.String()) + } + } + if testing.Verbose() { + t.Logf("stdout:\n%s", stdout.String()) + t.Logf("stderr:\n%s", stderr.String()) + } + return stdout.String(), exit +} + +func TestE2EVersion(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + + out, exit := runPrenup(t, bin, dir, "version") + assert.Equal(t, 0, exit) + assert.Contains(t, out, "prenup") +} + +func TestE2ERunWithNoChanges(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + + cfg := `version: 2 +tasks: + - name: "Echo" + command: "echo hello" + default_selected: true +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte(cfg), 0o600)) + // Commit the config so the worktree is clean; with no staged/unstaged/ + // untracked files we exercise the "no relevant files changed" path. + mustRunGit(t, dir, "add", ".prenup.yaml") + mustRunGit(t, dir, "commit", "-m", "add config") + + out, exit := runPrenup(t, bin, dir, "run") + assert.Equal(t, 0, exit) + assert.Contains(t, out, "No relevant files changed") +} + +func TestE2ERunPassingTaskMarkdownOutput(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) + + cfg := `version: 2 +clean_worktree: false +tasks: + - name: "Echo" + command: "echo prenup-ran" + default_selected: true + per_module: true +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte(cfg), 0o600)) + + out, exit := runPrenup(t, bin, dir, "run", "--no-clean-worktree") + assert.Equal(t, 0, exit, "stdout: %s", out) + assert.Contains(t, out, "prenup-ran") + assert.Contains(t, out, "1 succeeded, 0 failed") + assert.Contains(t, out, "## Prenup") +} + +func TestE2ERunFailingTaskBlocksCommit(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) + + cfg := `version: 2 +clean_worktree: false +tasks: + - name: "Fail" + command: "echo bad && exit 1" + default_selected: true + per_module: true +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte(cfg), 0o600)) + + out, exit := runPrenup(t, bin, dir, "run", "--no-clean-worktree") + assert.Equal(t, 1, exit, "expected non-zero exit on failure") + assert.Contains(t, out, "0 succeeded, 1 failed") +} + +func TestE2ERunJSONOutput(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) + + cfg := `version: 2 +clean_worktree: false +tasks: + - name: "Echo" + command: "echo hello" + default_selected: true + per_module: true +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte(cfg), 0o600)) + + out, exit := runPrenup(t, bin, dir, "run", "--output", "json", "--no-clean-worktree") + assert.Equal(t, 0, exit) + + lines := strings.Split(strings.TrimRight(out, "\n"), "\n") + require.GreaterOrEqual(t, len(lines), 2) + + // First line must be the agent_hint bootstrap so a cold-start consumer + // can identify the stream before any runner events arrive. + var hint map[string]any + require.NoError(t, json.Unmarshal([]byte(lines[0]), &hint)) + assert.Equal(t, "agent_hint", hint["type"]) + assert.Equal(t, "prenup", hint["tool"]) + + var first map[string]any + require.NoError(t, json.Unmarshal([]byte(lines[1]), &first)) + assert.Equal(t, "run_started", first["type"]) + + var last map[string]any + require.NoError(t, json.Unmarshal([]byte(lines[len(lines)-1]), &last)) + assert.Equal(t, "run_completed", last["type"]) + // exit_code is omitted when zero (omitempty); its absence means success. + if v, ok := last["exit_code"]; ok { + f, isFloat := v.(float64) + require.True(t, isFloat, "exit_code should be a JSON number") + assert.InDelta(t, 0.0, f, 0.0001) + } +} + +func TestE2EPlanJSON(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) + + cfg := `version: 2 +tasks: + - name: "Echo" + command: "echo hi" + default_selected: true + per_module: true +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte(cfg), 0o600)) + + out, exit := runPrenup(t, bin, dir, "plan", "--output", "json", "--all") + assert.Equal(t, 0, exit) + + var plan map[string]any + require.NoError(t, json.Unmarshal([]byte(out), &plan)) + assert.NotEmpty(t, plan["tasks"]) +} + +func TestE2EInstallUninstallRoundTrip(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + + // Use --force in case git init created a template hook under + // core.hooksPath or a default .git/hooks/pre-commit. + _, exit := runPrenup(t, bin, dir, "install", "--force") + require.Equal(t, 0, exit) + data, err := os.ReadFile(filepath.Join(dir, ".git", "hooks", "pre-commit")) //nolint:gosec // G304: path under test TempDir. + require.NoError(t, err) + assert.Contains(t, string(data), "prenup-managed-hook") + + _, exit = runPrenup(t, bin, dir, "uninstall") + require.Equal(t, 0, exit) + _, err = os.Stat(filepath.Join(dir, ".git", "hooks", "pre-commit")) + assert.True(t, os.IsNotExist(err)) +} + +func TestE2EMigrateV1(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + + v1 := `exclude: + - .github +tasks: + - name: "Tests" + default_selected: true + command: "go test ./..." + per_module: true +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yml"), []byte(v1), 0o600)) + + _, exit := runPrenup(t, bin, dir, "migrate") + require.Equal(t, 0, exit) + + data, err := os.ReadFile(filepath.Join(dir, ".prenup.yaml")) //nolint:gosec // G304: path under test TempDir. + require.NoError(t, err) + assert.Contains(t, string(data), "version: 2") + assert.Contains(t, string(data), "Tests") +} + +func TestE2EConfigValidate(t *testing.T) { + bin := buildBinary(t) + dir := t.TempDir() + initRepo(t, dir) + + cfg := `version: 2 +tasks: + - name: "Echo" + command: "echo" +` + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte(cfg), 0o600)) + + out, exit := runPrenup(t, bin, dir, "config", "validate") + assert.Equal(t, 0, exit) + assert.Contains(t, out, "OK:") +} diff --git a/cmd/prenup/main.go b/cmd/prenup/main.go new file mode 100644 index 0000000..56fbb9f --- /dev/null +++ b/cmd/prenup/main.go @@ -0,0 +1,16 @@ +// Package main is the entry point for the prenup binary. +// +// Prenup is an interactive, configuration-driven Git pre-commit hook utility. +// See README.md and docs/PRD.md for the product description and docs/ for +// reference material (config schema, event schema, future work). +package main + +import ( + "os" + + "github.com/c2fo/prenup/internal/cli" +) + +func main() { + os.Exit(cli.Execute()) +} diff --git a/docs/FUTURE.md b/docs/FUTURE.md new file mode 100644 index 0000000..d99fee6 --- /dev/null +++ b/docs/FUTURE.md @@ -0,0 +1,165 @@ +# Prenup — future work + +This file tracks deliberate non-goals of v2 that remain good ideas for +follow-up releases. Nothing here is committed; think of it as a backlog with +rationale. + +## Correctness & reliability + +### Continue-on-error within a task +Today (v2), the first failing module in a `per_module` task cancels its +remaining modules. Offer an opt-in `fail_fast: false` per task so developers +see every failing module in one pass with an aggregated summary. + +**Acceptance:** per-task config flag; runner collects all module errors; +final `task_completed` event carries the aggregate. + +### Concurrency safety — shipped in v2 +`prenup run` takes an OS-level advisory lock on `.git/prenup.lock` before +executing any tasks. A second concurrent invocation exits non-zero with a +clear "another prenup run is already in progress for this repository" +message, including the lock-file path. The lock is released automatically +on process exit (including crashes / kill -9), so there is no stale-PID +file to clean up. `--dry-run` skips the lock so planning probes don't +contend with a real commit-time run. + +### Staging guardrails +- Warn when a `stage_output: true` task modifies files outside its + `output_patterns` (likely a config bug). +- Warn when `output_patterns` matched zero files for several runs (stale + pattern). +- Refuse to stage files covered by `.gitignore`. + +## Developer experience + +### Per-task skip in the runner UI +Today `s` skips everything. Add an in-flight "press `n` to skip this task" +affordance in the runner model, emitting a `task_completed` with +`status: skipped`. + +### `PRENUP_SKIP` env var +One-off bypass without editing config. Comma-separated task names; merged +with `--task` exclusions. + +### Caching / incremental skip +For deterministic tasks (doc generation), skip when the hash of their +inputs (files matching `paths`) is unchanged since the last successful run. +Persist hash in `.git/prenup-cache/`. + +### Result history & `prenup stats` +Persist per-task duration per run. Surface "slower than average" indicators +in the post-run summary and a `prenup stats` subcommand for trend analysis. + +### Better failure presentation +- Highlight failing lines (regex on `FAIL`, `error:`, etc.) at the top of the + summary. +- Embed `file:line` jumps where compilers produce them. + +### UI affordances +- "Module N of M" progress indicator per running task. +- Wall-clock elapsed per running task. +- Colorblind-friendly icons (don't rely on color alone). + +## Configuration surface + +### `extends` / `include` / profiles +- `extends:` a shared base config (org-wide defaults). +- `include:` merge multiple files (e.g. `.prenup.d/*.yaml`). +- `profiles: { fast: [...], full: [...] }` with `prenup run --profile`. + +### Task-level DAG parallelism +Add `depends_on` to enable independent tasks to run concurrently while +serializing generators before linters. v2 is per-module-within-task only. + +### Pluggable module detectors +Beyond `module_markers`, introduce named detector plugins (Go, Node, +Python, Rust) that encode per-language intelligence (workspace detection, +monorepo conventions) rather than just a filename check. + +### Env pass-through allowlist +Today, tasks inherit prenup's environment, which under a Git hook is often +minimal. Add `pass_env: [HOME, PATH, GOPATH]` plus a globally-documented +allowlist for predictable behavior. + +### Per-developer overrides +`.prenup.local.yaml` layered on top of `.prenup.yaml` for individual +preferences (e.g. disable a slow task locally) without affecting the team +config. + +## Observability + +### Structured logging beyond event stream +In addition to the NDJSON event mode, offer an opt-in structured log file +(`--log-file PATH`) that captures the full run for later inspection — useful +for bots that need to store the trace but don't want to consume it live. + +### Opt-in telemetry +Aggregate anonymized stats on which tasks fail most often, average durations, +skip rates. Local-only unless explicitly opted in. + +## Tooling & safety + +### TUI snapshot tests +The Bubble Tea models have intricate state (selection cancel vs skip, runner +fail-fast, window resize). Add scripted input + golden-frame tests to prevent +regressions like the cancel-allowed-commit bug historically seen in v1. + +### Hook chaining edge cases +Today `install --chain` runs `pre-commit.local` before prenup. Handle the +inverse (run prenup first, then chain) behind a flag. Also add detection for +common managed pre-commit frameworks (husky, pre-commit.com) and offer a +clean compose story. + +### Version check refinements +Distinguish "no network" (silent) from "token invalid" (one-line warning) so +misconfigured tokens get noticed without adding noise to offline setups. + +### Inter-task parallelism +Today the runner serializes tasks (`Run tests` → `Run golangci-lint` → ...) and +only fans out *within* a task across modules. For most Go repos these tasks +are independent (different binaries, different file sets, no shared mutable +state) and a developer laptop has plenty of idle headroom while a single test +run is going. A typical hook of `15s test + 8s lint + 1s changelog ≈ 24s` +collapses to `max(15, 8, 1) = 15s` if those tasks run concurrently — a 30–50% +wall-clock cut on every commit. + +Why it isn't on by default yet: +- **Resource contention can defeat the math.** Both `go test` and + `golangci-lint` already spawn `GOMAXPROCS` workers each. Running them + concurrently doubles the compile-worker count fighting for cores and the + Go build cache, so the wall-clock saving sometimes flattens or inverts. + Same hazard `make -j` users hit. +- **Output presentation needs work.** The Bubble Tea TUI assumes one + "currently running" task; parallel tasks need either per-task panes, + line prefixing, or per-task output buffering (which loses the live-stream + property we deliberately preserved). Markdown and JSON modes already + carry per-task identifiers and would handle interleaving cleanly. +- **Stash and `stage_output` semantics get subtler.** Two tasks writing + files into the worktree concurrently can race on which task gets + attribution for a generated file; the per-task `beforeTracked` snapshot + needs review under concurrent execution. +- **Failure semantics need a knob.** Today a failing task lets siblings + finish. Under parallelism we'd want `cancel_siblings_on_failure: true|false` + so a long failing test doesn't kill a near-finished lint that would have + produced useful output. + +Proposed shape (when we do this): +- New top-level `task_parallelism: 1` (default — preserves today's behavior). + Setting it to e.g. `3` allows up to N tasks to run concurrently, capped + further by the existing `max_parallelism` so the *total* concurrent + commands across all tasks is bounded. +- New per-task `serial: true` escape hatch for tasks that must run after + prior tasks complete (e.g. a final "rebuild generated code" step that + depends on tests passing). +- Default to off; recommend enabling per repo after measuring the actual + wall-clock impact, since the contention curve is workload-dependent. +- When `task_parallelism > 1` is set with `--output human`, either degrade + the TUI to a per-task running-list (no live stream) or emit a notice + recommending `--output markdown` / `--output json` for cleaner interleaving. + +## Explicitly **not** planned + +- **Windows support.** Out of scope for v2. Many prenup primitives assume + bash, POSIX paths, and the platform's signal semantics; a Windows port + would require a non-trivial rewrite of exec and IO and is not on the + roadmap. diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 0000000..1275fde --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,330 @@ +# Prenup v2 — Product Requirements Document + +## 1. Overview + +**Prenup** is an interactive, configuration-driven Git pre-commit hook utility. +It gives developers a fast, visible, and selective way to run quality checks +(tests, linters, doc generators, custom scripts) immediately before a commit +lands — scoped intelligently to only the parts of the repository that actually +changed. + +Prenup targets repositories where running every check on every commit is too +slow or too noisy, and where a silent pre-commit hook either gets in the way +or gets bypassed. Prenup splits the difference: it shows the developer exactly +what is about to run, lets them choose, streams live results, and only blocks +the commit when something genuinely fails. + +While usable in any repository, Prenup is purpose-built for **Go monorepos and +multi-module repositories**, where "what changed" naturally maps to one or +more Go modules. v2 generalizes the module concept via configurable +`module_markers`, opening the door to polyglot repos without breaking the +core experience. + +v2 is also an explicit acknowledgment that prenup is used both by humans and +by agentic tooling. Output is TUI-first when run interactively, but +auto-degrades to a structured markdown digest when piped and can emit a +stable NDJSON event stream on request. + +## 2. Goals & Non-Goals + +### Goals +- Give developers a single, declarative configuration (`.prenup.yaml`) that + describes pre-commit checks for the whole team. +- Run only what is relevant to the current commit, scoped per changed module + when appropriate and further narrowed by per-task path filters. +- Make the developer an active participant: visible task list, opt-in per + task, live output, fast escape hatch. +- Block commits that fail real checks; never block on infrastructural issues + (network, version check, stash failure, etc.). +- Automatically include generated artifacts (docs, mocks, generated code) in + the commit so the developer doesn't have to remember `git add`. +- Make the pre-commit experience safe: tasks see exactly the content that + will be committed, not a dirty worktree. +- Be usable by both humans and agentic tooling: the same binary, same + behavior, different output shape. +- Stay lightweight: a single binary, no daemon, no shared state, no server. + +### Non-Goals +- Not a general-purpose task runner or build system. +- Not a CI replacement. +- Does not generate or enforce repository structure beyond reading + `.prenup.yaml` and the configured `module_markers`. +- Does not author commits or modify staged content beyond optionally + `git add`-ing declared output files. +- Does not support Windows in v2. + +## 3. Target Users & Use Cases + +### Primary users +- A developer in a Go repository (often a monorepo) who commits frequently + and wants pre-commit feedback that is fast, selective, and obvious. +- An AI coding agent driving `git commit` from an automated loop, consuming + prenup's markdown or JSON output to decide what to do next. + +### Primary use cases +1. **Run the right tests, only.** Only the modules with changes get tested or + linted, not the entire repo. +2. **Generated-file hygiene.** Automatically regenerate docs, mocks, or + protobuf for changed modules and stage the regenerated files. +3. **Repo-wide guardrails.** Run a single check (e.g. CHANGELOG enforcement) + once per commit. +4. **Interactive override.** Quickly skip a specific task for a one-off + commit without disabling the hook. +5. **Safe checks.** Ensure tests run against the exact content being + committed (not a dirty worktree). +6. **Agentic commits.** An agent runs `prenup run --output json --task "Run + tests"` and parses the event stream to react to failures programmatically. + +## 4. User Experience + +### 4.1 Installation +- `go install github.com/c2fo/prenup/cmd/prenup@latest` +- `prenup init` scaffolds a starter `.prenup.yaml` based on simple repo + inspection (Go modules, `.golangci.yml`, `Makefile`). +- `prenup install` writes `.git/hooks/pre-commit`. If another hook exists, + the developer is offered `replace`, `chain`, or `force`. Non-interactive + flags are available for automation; in non-interactive contexts (no TTY, + or `--no-interactive`) `install` fails fast on conflict instead of + hanging on a prompt. `--use-path` embeds `prenup` (resolved via `PATH` + at commit time) instead of the absolute path of the binary that ran + `install`, which is useful when the binary may move between installs. + +After this, Prenup is invisible until the developer runs `git commit`. + +### 4.2 Commit-time flow + +1. **Version banner** — Prenup prints its version and a non-blocking notice + when a newer release exists. +2. **Change discovery** — staged, unstaged, and untracked files minus + `exclude` patterns. +3. **Module discovery** — the nearest ancestor with a configured marker + (`module_markers`, default `[go.mod]`). +4. **Early exits (non-blocking)** — no relevant files or no modules → + commit proceeds. +5. **Interactive task selection (human mode)** — version, update warning, + module list, task checklist. Controls: + - arrow keys to move + - `space` to toggle + - `enter` to confirm + - `s` to skip all (commit proceeds) + - `q` / `ctrl+c` to cancel (commit blocked) +6. **Non-interactive modes** — markdown and JSON modes skip the selection UI + and run `default_selected` tasks (or the explicit `--task` set). +7. **Concurrency lock** — Prenup takes an OS-level advisory lock on + `.git/prenup.lock`. A second concurrent `prenup run` against the same + repo (e.g. a Git GUI that double-fires `git commit`) exits non-zero + with a clear "another prenup run is already in progress" message + instead of racing on the worktree. The lock auto-releases on process + exit, including crashes. `--dry-run` skips the lock. +8. **Stash-and-restore** — when `clean_worktree: true` (default), unstaged + changes are stashed before tasks run and restored afterward. +9. **Task execution** — tasks run sequentially; per-module tasks fan out + across modules concurrently up to `max_parallelism`. Live output streams + to the user. On first failure within a task, remaining modules for that + task are skipped and execution continues with the next task. +10. **Output staging** — `stage_output: true` tasks get their + `output_patterns` matches `git add`-ed post-success. The "before" + snapshot is taken immediately before each such task, so files + generated by an earlier task are not mis-attributed; for `per_module` + tasks, staging is further restricted to files under the task's + modules. +11. **Post-run summary** — version, update warning, full output, per-task + status with durations. +12. **Commit decision** — zero if everything passed or was intentionally + skipped; one otherwise. + +### 4.3 Skip / bypass +- `s` during selection → commit proceeds with no tasks run. +- `q` / `ctrl+c` during selection → commit blocked. +- `q` / `ctrl+c` during execution → the running task receives `SIGTERM` + with a short grace period to flush output and clean up before being + forcefully killed; commit blocked if anything failed or did not + complete. +- `git commit --no-verify` → Prenup not invoked at all. + +### 4.4 Visibility +- Everything important prints to stdout; stderr is reserved for diagnostics. +- Version prints at startup and again in the post-run summary so it survives + alt-screen teardown in wrapper clients. +- Task output streams live; the developer never waits for a long-running + command to finish to see what it's doing. +- The TUI resizes dynamically and starts small to behave well in integrated + terminals (VS Code, Windsurf) whose reported sizes can disagree. + +## 5. Configuration + +Single file, `.prenup.yaml` (or `.prenup.yml`), at the repository root. + +Glob patterns in `exclude`, `paths`, `paths_ignore`, and `output_patterns` +are validated at load time; an invalid pattern fails the run with a +pointer to the offending field rather than silently never matching. +`prenup config validate` exposes the same check as a standalone command. + +### 5.1 Top-level keys +- `version: 2` — required *integer* (not the string `"2"`); guards + against accidentally loading a v1 config. +- `module_markers` — filenames that define a module. Default `[go.mod]`. +- `exclude` — doublestar globs filtering change detection. +- `clean_worktree` — stash unstaged changes around task runs. Default `true`. +- `max_parallelism` — bound on per-module fan-out within a task. Default + `0`, meaning `NumCPU`. +- `output` — default output mode: `auto`, `human`, `markdown`, `json`. +- `tasks` — the ordered task list. + +### 5.2 Per-task fields +- `name` *(required)* — display name. +- `default_selected` — pre-checked in the selection UI. +- `command` *(required)* — `bash -c`-executed command with template + expansion. +- `per_module` — run once per changed module (default working dir is the + module root) or once globally. +- `working_dir` — override for the command's working directory; template + variables supported. +- `paths` / `paths_ignore` — doublestar filters on the changed-file set the + task applies to. When no files match, the task is skipped. +- `output_patterns` — files the task may create; used by `stage_output`. +- `stage_output` — stage newly-created matches of `output_patterns`. The + before-snapshot is taken per task, and for `per_module` tasks staging + is further restricted to files under the task's modules so a generator + in module `a/` will not auto-stage files produced under module `b/`. +- `parallel` — whether per-module iterations may run concurrently. Defaults + to `true` for per-module tasks. +- `clean_worktree` — override the repo-level default. +- `env` — map of environment variables to inject. + +### 5.3 Template variables + +| Variable | Description | +|---|---| +| `{{.repo_root}}` | Absolute path to the repo root. | +| `{{.module_root}}` | Absolute path to the current module. | +| `{{.module_path}}` | Relative path to the module from the repo root. | +| `{{.module_name}}` | Basename of the module directory. | + +## 6. Behavioral Requirements + +### 6.1 Change detection +- Staged, unstaged, and untracked files are all considered "changed" for + triggering and module discovery. +- `exclude` uses doublestar globs. +- Per-task `paths` / `paths_ignore` further restrict which files a task + sees; this also restricts the module set `per_module` tasks fan out + across. +- A file is "relevant" if it is changed and not excluded. + +### 6.2 Module detection +- A module is the nearest ancestor containing any `module_markers` file. +- Modules are deduplicated and sorted. +- If no modules are detected, Prenup exits zero with an explanatory message. +- `--all` bypasses change detection and feeds a synthetic `.` module. + +### 6.3 Task scheduling +- Tasks run sequentially in configuration order. +- Per-module iterations within a task fan out concurrently when + `parallel: true` and `max_parallelism > 1`, bounded by the configured cap. +- The first module-level failure in a task skips remaining modules for that + task and cancels their in-flight work; execution continues with the next + task. +- Cancellation (Ctrl-C, parent timeout, fail-fast within a task) sends + `SIGTERM` to the running command and allows a short grace period for + it to flush output and exit cleanly before forcing termination. + +### 6.4 Stash-and-restore +- When `clean_worktree: true`, Prenup runs `git stash push --include-untracked + --keep-index` before tasks begin and pops the stash on exit. +- Failure to stash emits a notice but does not block the run. +- Per-task `clean_worktree` overrides the repo-level default. + +### 6.5 Output staging +- Before *each* `stage_output` task runs, Prenup snapshots the set of + tracked files. The per-task snapshot prevents files generated by an + earlier task in the same run from being attributed to this one. +- After success, files that are newly present in `git status` and match any + `output_patterns` are `git add`-ed. +- Pre-existing unstaged changes are not promoted. +- For `per_module` tasks, staging is further restricted to files under the + task's modules. + +### 6.6 Exit codes +- `0` — commit proceeds: nothing to do, user skipped via `s`, all tasks + succeeded or were intentionally skipped. +- `1` — commit blocked: config error (including invalid glob patterns), + change detection error, user cancel, any task failure, or another + `prenup run` already holding the per-repo lock. + +### 6.7 Version check +- Best-effort, short-timeout GitHub Releases query. +- Tokens picked up in order: `PRENUP_GITHUB_TOKEN`, `GITHUB_TOKEN`, + `GH_TOKEN`. Required only for private repos. +- Network, auth, or format failures are swallowed silently. +- When a newer version exists, a single-line warning is displayed. + +### 6.8 Concurrency safety +- Before any tasks run, Prenup acquires a non-blocking OS-level advisory + lock on `.git/prenup.lock`. +- A second concurrent `prenup run` against the same repository exits + non-zero with a clear "another prenup run is already in progress" + message including the lock-file path; it does not wait. +- The lock auto-releases on process exit, including crashes and + `kill -9`, so there is no stale-PID file to clean up. +- `--dry-run` skips the lock so a planning probe never blocks a real + commit-time run. + +## 7. Output modes + +The same run produces identical events; the rendering differs: + +- **human** — Bubble Tea TUI with a live task checklist and scrolling output + viewport. Post-run summary printed after alt-screen exit. +- **markdown** — plain-text streaming output during execution followed by a + structured markdown digest: + - `[prenup] â€Ļ` self-describing preamble identifying the tool, linking + to docs, and pointing at the structured (`--output json`) mode + - `## Prenup v2.x.y` header + - summary counts + - `### Task: ...` sections with status, duration, modules, and tail of + output on failure + - "Next steps" block on failure that disambiguates prenup-vs-git + attribution and documents the `--no-verify` bypass +- **json** — NDJSON event stream on stdout; one JSON object per line. Stable + schema documented in [docs/SCHEMA.md](docs/SCHEMA.md). The very first + line is an `agent_hint` event carrying the same orienting context the + markdown preamble provides, so a cold-start consumer can identify the + stream without prior knowledge of prenup. + +Auto-detection: TTY → `human`; piped or `NO_COLOR` or `TERM=dumb` → +`markdown`; `json` is always explicit. + +The agent-orienting strings and the `agent_hint` payload are intentionally +absent from the human TUI: an interactive operator already knows what +prenup is and would only see them as noise. + +## 8. Constraints & Assumptions + +- The host has `git` and `bash` on `PATH`. +- The current directory is inside a git repository. +- `.prenup.yaml` lives at the repository root. +- Commands run via `bash -c`; shell features are available. +- Linux and macOS only in v2. + +## 9. Success Criteria + +1. Developers leave the hook installed rather than disabling it. +2. Trivial commits complete in seconds. +3. CI failures that a pre-commit task could have caught drop noticeably after + adoption. +4. Teams converge on a single shared `.prenup.yaml`. +5. Agent-driven workflows successfully parse `--output json` and react to + failures without needing a human in the loop. + +## 10. Out of scope / future considerations + +See [docs/FUTURE.md](docs/FUTURE.md) for a full catalog. Highlights: + +- Task-level DAG parallelism with `depends_on`. +- Aggregated failure summary across modules within a task. +- Caching / incremental skip for deterministic tasks. +- Plugin ecosystem beyond `module_markers`. +- `prenup stats` history and timing. +- Per-developer overrides layered on the team config. +- Windows support. diff --git a/docs/SCHEMA.md b/docs/SCHEMA.md new file mode 100644 index 0000000..cb02412 --- /dev/null +++ b/docs/SCHEMA.md @@ -0,0 +1,181 @@ +# Prenup v2 schemas + +This document describes the two stable contracts prenup exposes: the config +file schema and the JSON event stream schema. + +## Config schema + +The authoritative machine-readable schema is +[assets/prenup.schema.json](../assets/prenup.schema.json). Editors with JSON +Schema support (VS Code YAML, IntelliJ, Zed) will offer autocomplete and +validation against it. + +### Top-level fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `version` | integer | — (required) | Must be the integer `2`. Quoted strings (`"2"`) are rejected with a hint. | +| `module_markers` | string[] | `[go.mod]` | Filenames whose presence marks a module directory. | +| `exclude` | string[] | `[]` | Doublestar globs filtering change detection. | +| `clean_worktree` | bool | `true` | Stash unstaged changes around task execution. | +| `max_parallelism` | int | `0` | Cap on per-module fan-out. `0` means `NumCPU`. | +| `output` | string | `auto` | Default output mode. One of `auto`, `human`, `markdown`, `json`. | +| `tasks` | task[] | — (required) | Ordered task list. | + +### Task fields + +| Field | Type | Description | +|---|---|---| +| `name` | string (required) | Display name and dedup key. | +| `command` | string (required) | Shell command, run via `bash -c`. | +| `default_selected` | bool | Pre-checked in the selection UI. | +| `per_module` | bool | Run once per changed module (default working dir is the module root). | +| `working_dir` | string | Override for the command working directory. Template variables supported. | +| `paths` | string[] | Doublestar globs; restrict the task's changed-file set. Empty = all. | +| `paths_ignore` | string[] | Doublestar globs; files to exclude from the task's set. | +| `output_patterns` | string[] | Globs used by `stage_output` to identify generated files. | +| `stage_output` | bool | After success, `git add` files matching `output_patterns` that are newly present. | +| `parallel` | bool | Whether per-module iterations may run concurrently. Defaults to `true` for `per_module` tasks. | +| `clean_worktree` | bool | Override the repo-level default. | +| `env` | map[string]string | Environment variables injected for the task. | + +### Template variables + +Available inside `command` and `working_dir`: + +- `{{.repo_root}}` — absolute path to the repo root. +- `{{.module_root}}` — absolute path to the current module. +- `{{.module_path}}` — module path relative to the repo root. +- `{{.module_name}}` — basename of the module. + +## JSON event stream + +`prenup run --output json` emits NDJSON: one JSON object per line on stdout. +The stream is stable: additive changes are permitted; fields may be added, +but existing fields keep their meaning and type across minor versions. + +`prenup plan --output json` is **not** NDJSON; it emits a single +pretty-printed JSON document. See "Plan output" below. + +### Common fields + +| Field | Type | Notes | +|---|---|---| +| `type` | string | Event kind (see below). | +| `time` | RFC3339 timestamp | Always present. | + +### Event kinds + +#### `agent_hint` +```json +{"type":"agent_hint","schema":"1","tool":"prenup","description":"Prenup is a Git pre-commit hook runner ...","homepage":"https://github.com/c2fo/prenup","hook_context":"This output is produced by the prenup pre-commit hook ...","bypass_hint":"Re-run `git commit` after fixing the issue, or use `git commit --no-verify` ...","stream_format":"ndjson","event_types_note":"Subsequent lines are runner events: ..."} +``` +- Always the **first** line of the stream, emitted before any runner event. +- Self-describing bootstrap so a consumer that has no prior knowledge of + prenup can identify the tool, find its docs, learn the stream format, and + know how to recover on failure -- all from the first line of output. +- `schema` versions the agent_hint payload itself; bumped when the shape + of the strings or surrounding fields changes. +- Consumers that don't care can skip any line whose `type` they don't + recognize and continue parsing as normal. +- **Not** emitted from `prenup plan --output json` (that command produces + a single JSON document, not an event stream). + +#### `run_started` +```json +{"type":"run_started","time":"...","version":"v2.0.0","repo_root":"/path/to/repo","modules":["pkg/foo"],"tasks":["Run tests"],"message":"Update available v2.0.1 ..."} +``` +- `repo_root` — absolute path of the git repository prenup was invoked + from. Anchors every subsequent task's `working_dir` so consumers do + not have to infer the root from substring matches. +- `modules` — final module list after exclude/path filters. +- `tasks` — tasks that will attempt to run. +- `message` — optional update-notice string; never blocks the run. + +#### `task_started` +```json +{"type":"task_started","time":"...","task":"Run tests","module":"pkg/foo","command":"go test ./...","working_dir":"/repo/pkg/foo"} +``` +- Emitted once per module per task invocation. +- `command` is the resolved shell command after template expansion + (`{{.repo_root}}`, `{{.module_root}}`, etc.). A consumer can copy/paste + it to reproduce the run without parsing `.prenup.yaml`. +- `working_dir` is the absolute path the command was run in. For + per-module tasks it is the module root; otherwise it is the value of + the task's `working_dir` (template-expanded) or the repo root. +- Both fields are omitted when empty (e.g. malformed task config). + +#### `line` +```json +{"type":"line","time":"...","task":"Run tests","module":"pkg/foo","stream":"stdout","text":"ok pkg/foo 0.12s"} +``` +- `stream` is `stdout` or `stderr`. +- `text` is one unredirected line; no trailing newline. +- Lines exceeding ~1 MB are split and a synthetic + `[prenup] output truncated: ... (line exceeded 1048576 bytes)` line is + emitted on the same stream, so consumers can tell that the source produced + an oversized line rather than silently losing data. + +#### `task_completed` +```json +{"type":"task_completed","time":"...","task":"Run tests","status":"done","duration_ms":120} +``` +- `status` is `done`, `failed`, or `skipped`. +- Emitted once per task after all its module iterations complete or are + skipped. +- For `skipped`, `message` may describe why. + +#### `notice` +```json +{"type":"notice","time":"...","message":"failed to restore stash: ..."} +{"type":"notice","time":"...","task":"Run tests","module":"pkg/b","message":"fail-fast: sibling module failed"} +``` +- Non-fatal diagnostic messages. Does not affect exit code. +- May carry `task` and `module` fields. In particular, when one module of a + `per_module` task fails, the remaining modules for that task are reported + via per-module `notice` events so consumers can see exactly which modules + were aborted by fail-fast. + +#### `run_completed` +```json +{"type":"run_completed","time":"...","succeeded":1,"failed":1,"exit_code":1,"failed_tasks":["Run tests"]} +``` +- Always the last event. `exit_code` matches the process exit code. +- `failed_tasks` — names of tasks that ended in `failed` status, in + selection order. Omitted when no task failed. Lets a consumer index + the failures in O(1) without rescanning every `task_completed` event. + +### Plan output (`prenup plan --output json`) + +Not NDJSON: a single pretty-printed JSON document describing the plan. + +```json +{ + "repo_root": "/abs/path", + "modules": ["pkg/foo"], + "tasks": [ + { + "name": "Run tests", + "selected": true, + "per_module": true, + "clean_worktree": true, + "parallel": true, + "modules": ["pkg/foo"] + } + ] +} +``` + +## Stability + +- The event `type` values are final. +- Existing fields may be extended with new enum values only in major + versions. +- New fields may be added at any time; consumers must ignore unknown fields. +- The config `version` integer tracks schema major versions. +- The embedded schema (consulted by `prenup config validate` and exported by + `prenup config schema`) and the public asset at + [`assets/prenup.schema.json`](../assets/prenup.schema.json) are guaranteed + to be byte-identical: a CI test fails the build if they ever diverge. + External consumers can safely fetch the asset URL without worrying about + drift from the binary's view of the schema. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6d3a3aa --- /dev/null +++ b/go.mod @@ -0,0 +1,43 @@ +module github.com/c2fo/prenup + +go 1.26 + +require ( + github.com/bmatcuk/doublestar/v4 v4.10.0 + github.com/charmbracelet/bubbles v1.0.0 + github.com/charmbracelet/bubbletea v1.3.10 + github.com/charmbracelet/lipgloss v1.1.0 + github.com/mattn/go-isatty v0.0.21 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 + golang.org/x/mod v0.32.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dd90eef --- /dev/null +++ b/go.sum @@ -0,0 +1,82 @@ +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= +github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc= +github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= +github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/cli/config_cmd.go b/internal/cli/config_cmd.go new file mode 100644 index 0000000..8420e31 --- /dev/null +++ b/internal/cli/config_cmd.go @@ -0,0 +1,69 @@ +package cli + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/git" +) + +// newConfigCmd houses configuration-related subcommands. +func newConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "config", + Short: "Inspect prenup configuration", + } + cmd.AddCommand(newConfigValidateCmd(), newConfigSchemaCmd()) + return cmd +} + +func newConfigValidateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "validate [path]", + Short: "Validate a .prenup.yaml file against the v2 schema", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + repoRoot, err := git.RepoRoot("") + if err != nil { + return err + } + var path string + if len(args) == 1 { + path = args[0] + } else { + p, err := config.Find(repoRoot) + if err != nil { + return err + } + if p == "" { + return fmt.Errorf("no config file found in %s", repoRoot) + } + path = p + } + cfg, err := config.Load(path, repoRoot) + if err != nil { + return err + } + if _, err := fmt.Fprintf(os.Stdout, "OK: %s parses cleanly (v%d, %d tasks)\n", + cfg.Path, cfg.Version, len(cfg.Tasks)); err != nil { + return err + } + return nil + }, + } + return cmd +} + +func newConfigSchemaCmd() *cobra.Command { + return &cobra.Command{ + Use: "schema", + Short: "Print the embedded JSON schema for .prenup.yaml", + RunE: func(cmd *cobra.Command, args []string) error { + _, err := os.Stdout.Write(config.Schema()) + return err + }, + } +} diff --git a/internal/cli/init.go b/internal/cli/init.go new file mode 100644 index 0000000..f852069 --- /dev/null +++ b/internal/cli/init.go @@ -0,0 +1,130 @@ +package cli + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/git" +) + +// newInitCmd scaffolds a starter .prenup.yaml from light repo inspection. +func newInitCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "init", + Short: "Scaffold a starter .prenup.yaml in the repository root", + RunE: func(cmd *cobra.Command, args []string) error { + return runInit(cmd) + }, + } + cmd.Flags().Bool("force", false, "overwrite an existing config file") + return cmd +} + +func runInit(cmd *cobra.Command) error { + force, _ := cmd.Flags().GetBool("force") + + repoRoot, err := git.RepoRoot("") + if err != nil { + return err + } + + target := filepath.Join(repoRoot, ".prenup.yaml") + if _, err := os.Stat(target); err == nil && !force { + return fmt.Errorf("%s already exists (use --force to overwrite)", target) + } + + cfg := config.DefaultConfig() + cfg.CleanWorktree = nil // let it inherit the default on load + + hasGoMod := scanForGoMod(repoRoot) + hasLint := scanForFile(repoRoot, ".golangci.yml", ".golangci.yaml") + hasMakefile := scanForFile(repoRoot, "Makefile") + + cfg.Exclude = []string{".github/**", "**/*.yaml", "**/*.yml"} + + if hasGoMod { + cfg.Tasks = append(cfg.Tasks, config.Task{ + Name: "Run tests", + DefaultSelected: true, + Command: "go test ./...", + PerModule: true, + Paths: []string{"**/*.go"}, + }) + if hasLint { + cfg.Tasks = append(cfg.Tasks, config.Task{ + Name: "Run golangci-lint", + DefaultSelected: true, + Command: "golangci-lint run --max-same-issues 0 ./...", + PerModule: true, + Paths: []string{"**/*.go"}, + }) + } + } + if hasMakefile { + cfg.Tasks = append(cfg.Tasks, config.Task{ + Name: "Check Makefile targets", + DefaultSelected: false, + Command: "make --dry-run help", + PerModule: false, + }) + } + if len(cfg.Tasks) == 0 { + cfg.Tasks = append(cfg.Tasks, config.Task{ + Name: "Example task", + DefaultSelected: false, + Command: "echo 'replace me with a real command'", + }) + } + + out, err := config.Marshal(cfg) + if err != nil { + return err + } + if err := os.WriteFile(target, out, 0o600); err != nil { + return err + } + if _, err := fmt.Fprintf(os.Stdout, "Wrote %s\n", target); err != nil { + return err + } + return nil +} + +func scanForGoMod(root string) bool { + found := false + _ = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return filepath.SkipDir + } + if d.IsDir() { + base := d.Name() + if strings.HasPrefix(base, ".") && base != "." { + return filepath.SkipDir + } + if base == "vendor" || base == "node_modules" { + return filepath.SkipDir + } + return nil + } + if d.Name() == "go.mod" { + found = true + return filepath.SkipAll + } + return nil + }) + return found +} + +func scanForFile(root string, names ...string) bool { + for _, name := range names { + if _, err := os.Stat(filepath.Join(root, name)); err == nil { + return true + } + } + return false +} diff --git a/internal/cli/install.go b/internal/cli/install.go new file mode 100644 index 0000000..29d34f1 --- /dev/null +++ b/internal/cli/install.go @@ -0,0 +1,191 @@ +package cli + +import ( + "bufio" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/mattn/go-isatty" + "github.com/spf13/cobra" + + "github.com/c2fo/prenup/internal/git" + "github.com/c2fo/prenup/internal/hook" +) + +// newInstallCmd writes .git/hooks/pre-commit. +func newInstallCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "install", + Short: "Install prenup as the pre-commit hook in this repository", + RunE: func(cmd *cobra.Command, args []string) error { + return runInstall(cmd) + }, + } + cmd.Flags().Bool("force", false, "overwrite any existing pre-commit hook without backup") + cmd.Flags().Bool("replace", false, "back up the existing hook and replace it") + cmd.Flags().Bool("chain", false, "save the existing hook as pre-commit.local and chain it before prenup") + cmd.Flags().String("binary", "", "path to the prenup binary (defaults to the currently running executable)") + cmd.Flags().Bool("use-path", false, + "invoke `prenup` from PATH inside the hook instead of recording an absolute path (good for shared dotfiles)") + cmd.Flags().Bool("non-interactive", false, "refuse to prompt; fail if a non-prenup hook exists and no mode flag was given") + return cmd +} + +// installFlags is the parsed view of `prenup install`'s flag set. +type installFlags struct { + force bool + replace bool + chain bool + binary string + usePath bool + nonInteractive bool +} + +func readInstallFlags(cmd *cobra.Command) installFlags { + f := installFlags{} + f.force, _ = cmd.Flags().GetBool("force") + f.replace, _ = cmd.Flags().GetBool("replace") + f.chain, _ = cmd.Flags().GetBool("chain") + f.binary, _ = cmd.Flags().GetString("binary") + f.usePath, _ = cmd.Flags().GetBool("use-path") + f.nonInteractive, _ = cmd.Flags().GetBool("non-interactive") + return f +} + +// resolveInstallBinary picks the binary path to embed in the hook script. +func resolveInstallBinary(f installFlags) (string, error) { + switch { + case f.usePath && f.binary != "": + return "", errors.New("--use-path and --binary are mutually exclusive") + case f.usePath: + return "prenup", nil + case f.binary != "": + return f.binary, nil + default: + return resolveBinary() + } +} + +// installModeFromFlags returns the explicit mode requested by --force/ +// --replace/--chain, defaulting to ModeAbort when none is supplied. +func installModeFromFlags(f installFlags) hook.Mode { + switch { + case f.force: + return hook.ModeForce + case f.replace: + return hook.ModeReplace + case f.chain: + return hook.ModeChain + } + return hook.ModeAbort +} + +func runInstall(cmd *cobra.Command) error { + f := readInstallFlags(cmd) + repoRoot, err := git.RepoRoot("") + if err != nil { + return err + } + binary, err := resolveInstallBinary(f) + if err != nil { + return err + } + + if err := installWithConflictResolution(repoRoot, binary, f); err != nil { + return err + } + + _, err = fmt.Fprintf(os.Stdout, "Installed pre-commit hook at %s\n", + filepath.Join(repoRoot, ".git", "hooks", "pre-commit")) + return err +} + +// installWithConflictResolution performs the install and, on ExistsError, +// either fails fast (non-interactive contexts) or prompts the user for a +// resolution mode and retries. +func installWithConflictResolution(repoRoot, binary string, f installFlags) error { + err := hook.Install(repoRoot, binary, installModeFromFlags(f)) + var existsErr *hook.ExistsError + if !errors.As(err, &existsErr) { + return err + } + if f.nonInteractive || !isatty.IsTerminal(os.Stdin.Fd()) { + return err + } + choice, promptErr := promptInstallMode(existsErr.Path) + if promptErr != nil { + return promptErr + } + if choice == hook.ModeAbort { + return errors.New("install aborted") + } + return hook.Install(repoRoot, binary, choice) +} + +func newUninstallCmd() *cobra.Command { + return &cobra.Command{ + Use: "uninstall", + Short: "Remove the prenup pre-commit hook", + RunE: func(cmd *cobra.Command, args []string) error { + repoRoot, err := git.RepoRoot("") + if err != nil { + return err + } + if err := hook.Uninstall(repoRoot); err != nil { + return err + } + if _, err := fmt.Fprintln(os.Stdout, "Pre-commit hook removed."); err != nil { + return err + } + return nil + }, + } +} + +// resolveBinary returns the absolute path to the currently running prenup +// binary. Prefer os.Executable; fall back to looking it up on PATH. +func resolveBinary() (string, error) { + exe, err := os.Executable() + if err == nil { + if abs, aerr := filepath.EvalSymlinks(exe); aerr == nil { + return abs, nil + } + return exe, nil + } + if p, lerr := exec.LookPath("prenup"); lerr == nil { + return p, nil + } + return "", fmt.Errorf("could not determine prenup binary path: %w", err) +} + +func promptInstallMode(existingPath string) (hook.Mode, error) { + // Stderr writes for an interactive prompt are routinely ignorable: a + // failing terminal would also fail the subsequent ReadString. + _, _ = fmt.Fprintf(os.Stderr, "A pre-commit hook already exists at %s.\n", existingPath) + _, _ = fmt.Fprintln(os.Stderr, "Choose how to proceed:") + _, _ = fmt.Fprintln(os.Stderr, " [r]eplace (back up existing hook and replace with prenup)") + _, _ = fmt.Fprintln(os.Stderr, " [c]hain (keep existing hook as pre-commit.local, run it before prenup)") + _, _ = fmt.Fprintln(os.Stderr, " [f]orce (overwrite without backup)") + _, _ = fmt.Fprintln(os.Stderr, " [a]bort") + _, _ = fmt.Fprint(os.Stderr, "> ") + + reader := bufio.NewReader(os.Stdin) + line, err := reader.ReadString('\n') + if err != nil { + return hook.ModeAbort, err + } + switch strings.ToLower(strings.TrimSpace(line)) { + case "r", "replace": + return hook.ModeReplace, nil + case "c", "chain": + return hook.ModeChain, nil + case "f", "force": + return hook.ModeForce, nil + default: + return hook.ModeAbort, nil + } +} diff --git a/internal/cli/migrate.go b/internal/cli/migrate.go new file mode 100644 index 0000000..e760444 --- /dev/null +++ b/internal/cli/migrate.go @@ -0,0 +1,69 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/git" +) + +// newMigrateCmd converts a v1 config into a v2 config. +func newMigrateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "migrate", + Short: "Convert a v1 .prenup.yml to a v2 .prenup.yaml", + RunE: func(cmd *cobra.Command, args []string) error { + return runMigrate(cmd) + }, + } + cmd.Flags().String("in", "", "input v1 config path (defaults to .prenup.yml in repo root)") + cmd.Flags().String("out", "", "output v2 config path (defaults to .prenup.yaml in repo root)") + cmd.Flags().Bool("force", false, "overwrite an existing output file") + return cmd +} + +func runMigrate(cmd *cobra.Command) error { + in, _ := cmd.Flags().GetString("in") + out, _ := cmd.Flags().GetString("out") + force, _ := cmd.Flags().GetBool("force") + + repoRoot, err := git.RepoRoot("") + if err != nil { + return err + } + + if in == "" { + in = filepath.Join(repoRoot, ".prenup.yml") + } + if out == "" { + out = filepath.Join(repoRoot, ".prenup.yaml") + } + + in = filepath.Clean(in) + data, err := os.ReadFile(in) + if err != nil { + return fmt.Errorf("reading %s: %w", in, err) + } + + _, v2bytes, err := config.MigrateV1(data) + if err != nil { + return err + } + + out = filepath.Clean(out) + if _, statErr := os.Stat(out); statErr == nil && !force { + return fmt.Errorf("%s already exists (use --force to overwrite)", out) + } + // out is a user-supplied destination path, cleaned above. + if err := os.WriteFile(out, v2bytes, 0o600); err != nil { + return fmt.Errorf("writing %s: %w", out, err) + } + if _, err := fmt.Fprintf(os.Stdout, "Migrated %s -> %s\n", in, out); err != nil { + return err + } + return nil +} diff --git a/internal/cli/plan.go b/internal/cli/plan.go new file mode 100644 index 0000000..63dd99f --- /dev/null +++ b/internal/cli/plan.go @@ -0,0 +1,155 @@ +package cli + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/discover" + "github.com/c2fo/prenup/internal/git" + "github.com/c2fo/prenup/internal/runner" + "github.com/c2fo/prenup/internal/ui" +) + +// newPlanCmd prints the plan for the current change set without executing it. +// Respects --output so agents can consume the plan as JSON. +func newPlanCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "plan", + Short: "Print what prenup would run for the current change set", + RunE: func(cmd *cobra.Command, args []string) error { + return runPlan(cmd) + }, + } + cmd.Flags().String("config", "", "path to .prenup.yaml") + cmd.Flags().String("output", "", "output mode: auto, human, markdown, or json") + cmd.Flags().Bool("all", false, "ignore change detection") + return cmd +} + +func runPlan(cmd *cobra.Command) error { + cfgPath, _ := cmd.Flags().GetString("config") + outStr, _ := cmd.Flags().GetString("output") + all, _ := cmd.Flags().GetBool("all") + + repoRoot, err := git.RepoRoot("") + if err != nil { + return err + } + gitRunner := git.New(repoRoot) + + if cfgPath == "" { + p, err := config.Find(repoRoot) + if err != nil { + return err + } + if p == "" { + return fmt.Errorf("no .prenup.yaml found in %s", repoRoot) + } + cfgPath = p + } + cfg, err := config.Load(cfgPath, repoRoot) + if err != nil { + return err + } + + var changedFiles []string + if !all { + changedFiles, err = discover.ChangedFiles(gitRunner, cfg.Exclude) + if err != nil { + return err + } + } + modules := discover.Modules(repoRoot, changedFiles, cfg.ModuleMarkers) + if all || (len(modules) == 0 && len(changedFiles) > 0) { + modules = []string{"."} + } + + plan := runner.BuildPlan(cfg, repoRoot, changedFiles, modules, nil) + + mode := ui.Resolve(config.OutputMode(strings.ToLower(outStr))) + switch mode { + case config.OutputJSON: + return renderPlanJSON(plan) + default: + return renderPlanText(plan, os.Stdout) + } +} + +func renderPlanJSON(plan runner.Plan) error { + type taskView struct { + Name string `json:"name"` + Selected bool `json:"selected"` + SkipReason string `json:"skip_reason,omitempty"` + PerModule bool `json:"per_module"` + CleanWorktree bool `json:"clean_worktree"` + Parallel bool `json:"parallel"` + Modules []string `json:"modules"` + } + type view struct { + RepoRoot string `json:"repo_root"` + Modules []string `json:"modules"` + Tasks []taskView `json:"tasks"` + } + + v := view{RepoRoot: plan.RepoRoot, Modules: plan.Modules} + for i := range plan.Tasks { + pt := &plan.Tasks[i] + mods := pt.Modules + if len(mods) == 1 && mods[0] == "" { + mods = nil + } + v.Tasks = append(v.Tasks, taskView{ + Name: pt.Task.Name, + Selected: pt.Selected, + SkipReason: pt.SkipReason, + PerModule: pt.Task.PerModule, + CleanWorktree: pt.CleanWorktree, + Parallel: pt.Parallel, + Modules: mods, + }) + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(v) +} + +func renderPlanText(plan runner.Plan, w *os.File) error { + if _, err := fmt.Fprintf(w, "Repository: %s\n", plan.RepoRoot); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "Modules: %s\n\n", strings.Join(plan.Modules, ", ")); err != nil { + return err + } + for i := range plan.Tasks { + pt := &plan.Tasks[i] + marker := "v" + if !pt.Selected { + marker = "-" + } + if _, err := fmt.Fprintf(w, "[%s] %s\n", marker, pt.Task.Name); err != nil { + return err + } + if pt.SkipReason != "" { + if _, err := fmt.Fprintf(w, " skipped: %s\n", pt.SkipReason); err != nil { + return err + } + } + if pt.Task.PerModule && pt.Selected { + if _, err := fmt.Fprintf(w, " per_module, modules: %s\n", + strings.Join(pt.Modules, ", ")); err != nil { + return err + } + } + if pt.Task.Command != "" { + if _, err := fmt.Fprintf(w, " command: %s\n", pt.Task.Command); err != nil { + return err + } + } + } + return nil +} diff --git a/internal/cli/root.go b/internal/cli/root.go new file mode 100644 index 0000000..9e29ae6 --- /dev/null +++ b/internal/cli/root.go @@ -0,0 +1,84 @@ +// Package cli defines the cobra command tree for the prenup binary. +package cli + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// Execute parses os.Args, dispatches to the chosen subcommand, and returns +// the desired process exit code. A zero code means success. +func Execute() int { + root := newRootCmd() + if err := root.Execute(); err != nil { + // cobra already prints its own errors; ensure we surface a non-zero + // exit. Individual commands that want to pick a specific code use + // exitCodeError below. + ec := &exitCodeError{} + if errors.As(err, &ec) { + return ec.code + } + return 1 + } + return 0 +} + +// exitCodeError lets a subcommand propagate a specific exit code without +// printing an extra usage summary. +type exitCodeError struct { + code int + err error +} + +func (e *exitCodeError) Error() string { + if e.err == nil { + return fmt.Sprintf("exit status %d", e.code) + } + return e.err.Error() +} + +func newRootCmd() *cobra.Command { + root := &cobra.Command{ + Use: "prenup", + Short: "Interactive pre-commit hook utility", + Long: `Prenup runs user-defined tasks (tests, linters, doc generators, custom scripts) +as a Git pre-commit hook, scoped to the modules that actually changed. + +When invoked with no subcommand, prenup behaves as the git hook entry point +and is equivalent to "prenup run". See the README for configuration.`, + SilenceUsage: true, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runRun(cmd, args, defaultRunOptions()) + }, + } + + // Run's flags live on root so they also apply to the default (no-subcommand) invocation. + addRunFlags(root) + + root.AddCommand( + newRunCmd(), + newPlanCmd(), + newInstallCmd(), + newUninstallCmd(), + newInitCmd(), + newMigrateCmd(), + newConfigCmd(), + newVersionCmd(), + ) + return root +} + +func newVersionCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print prenup version and exit", + RunE: func(cmd *cobra.Command, args []string) error { + _, err := fmt.Fprintf(os.Stdout, "prenup %s\n", ResolvedVersion()) + return err + }, + } +} diff --git a/internal/cli/run.go b/internal/cli/run.go new file mode 100644 index 0000000..9a9bf1a --- /dev/null +++ b/internal/cli/run.go @@ -0,0 +1,409 @@ +package cli + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/discover" + "github.com/c2fo/prenup/internal/git" + "github.com/c2fo/prenup/internal/lock" + "github.com/c2fo/prenup/internal/runner" + "github.com/c2fo/prenup/internal/ui" + "github.com/c2fo/prenup/internal/ui/human" + "github.com/c2fo/prenup/internal/ui/jsonout" + "github.com/c2fo/prenup/internal/ui/markdown" + "github.com/c2fo/prenup/internal/versioncheck" +) + +// runOptions holds the resolved flag values for `prenup run`. +type runOptions struct { + configPath string + outputMode string + taskNames []string + all bool + noInteractive bool + noCleanWorktree bool + parallelism int + dryRun bool +} + +func defaultRunOptions() runOptions { return runOptions{} } + +// addRunFlags registers shared flags on cmd. Invoked for both the root command +// and the explicit `run` subcommand. +func addRunFlags(cmd *cobra.Command) { + f := cmd.Flags() + f.String("config", "", "path to .prenup.yaml (defaults to repo root)") + f.String("output", "", "output mode: auto, human, markdown, or json") + f.StringSlice("task", nil, "run only the named task(s); repeatable") + f.Bool("all", false, "ignore change detection and run against all configured module markers") + f.Bool("no-interactive", false, "skip the selection UI; run default_selected tasks") + f.Bool("no-clean-worktree", false, "disable stash-and-restore around task execution") + f.Int("parallelism", 0, "max concurrent per-module task runs; 0 = NumCPU") + f.Bool("dry-run", false, "print what would run without executing commands") +} + +func readRunOptions(cmd *cobra.Command) runOptions { + f := cmd.Flags() + cfgPath, _ := f.GetString("config") + out, _ := f.GetString("output") + tasks, _ := f.GetStringSlice("task") + all, _ := f.GetBool("all") + noInt, _ := f.GetBool("no-interactive") + noClean, _ := f.GetBool("no-clean-worktree") + par, _ := f.GetInt("parallelism") + dry, _ := f.GetBool("dry-run") + return runOptions{ + configPath: cfgPath, + outputMode: out, + taskNames: tasks, + all: all, + noInteractive: noInt, + noCleanWorktree: noClean, + parallelism: par, + dryRun: dry, + } +} + +func newRunCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "Run the pre-commit checks without committing", + RunE: func(cmd *cobra.Command, args []string) error { + return runRun(cmd, args, readRunOptions(cmd)) + }, + } + addRunFlags(cmd) + return cmd +} + +// runRun is the entry point used by both `prenup` (default) and `prenup run`. +func runRun(cmd *cobra.Command, _ []string, opts runOptions) error { + if cmd.Flags().NFlag() > 0 { + opts = readRunOptions(cmd) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + repoRoot, err := git.RepoRoot("") + if err != nil { + return fmt.Errorf("locating git repository: %w", err) + } + gitRunner := git.New(repoRoot) + + cfg, err := loadRunConfig(repoRoot, opts.configPath) + if err != nil { + return err + } + + // Discover changes and modules. --all ignores change detection. + disc, err := discoverChangesAndModules(gitRunner, repoRoot, cfg, opts.all) + if err != nil { + return err + } + if disc.done { + return printlnSafe(disc.message) + } + + // Resolve output mode. + requested := config.OutputMode(strings.ToLower(opts.outputMode)) + if requested == "" { + requested = cfg.Output + } + mode := ui.Resolve(requested) + + verInfo := maybeVersionCheck(ctx) + + sel, err := resolveSelection(opts, mode, disc.modules, cfg, verInfo) + if err != nil { + return err + } + if sel.done { + if sel.exitCode != 0 { + return &exitCodeError{code: sel.exitCode, err: errors.New(sel.message)} + } + return printlnSafe(sel.message) + } + + plan := runner.BuildPlan(cfg, repoRoot, disc.changedFiles, disc.modules, sel.selected) + + cleanWorktree := cfg.CleanWorktreeEnabled() + if opts.noCleanWorktree { + cleanWorktree = false + } + + runOpts := runner.Options{ + Git: gitRunner, + Version: verInfo.version, + UpdateNotice: verInfo.notice, + MaxParallelism: coalescePar(opts.parallelism, cfg.MaxParallelism), + CleanWorktree: cleanWorktree, + DryRun: opts.dryRun, + } + + release, err := acquireRepoLock(repoRoot, opts.dryRun) + if err != nil { + return err + } + defer release() + + result, err := runWithMode(ctx, mode, plan, runOpts) + if err != nil { + return err + } + if result.ExitCode != 0 { + return &exitCodeError{code: result.ExitCode} + } + return nil +} + +// printlnSafe writes msg to stdout when non-empty. Returns the underlying +// write error so callers can propagate it. +func printlnSafe(msg string) error { + if msg == "" { + return nil + } + _, err := fmt.Fprintln(os.Stdout, msg) + return err +} + +// acquireRepoLock takes the per-repo advisory lock that serializes +// concurrent `prenup run` invocations. Dry-run skips the lock since it +// performs no destructive work; this lets a planning probe run alongside a +// real commit-time invocation. The returned release func is always safe to +// call (it is a no-op when no lock was taken), so callers can defer it +// unconditionally. +func acquireRepoLock(repoRoot string, dryRun bool) (func(), error) { + if dryRun { + return func() {}, nil + } + held, err := lock.Acquire(repoRoot) + if err != nil { + if errors.Is(err, lock.ErrContended) { + return nil, &exitCodeError{code: 1, err: fmt.Errorf( + "%w (lock file: %s)", err, filepath.Join(repoRoot, ".git", lock.LockFileName))} + } + return nil, err + } + return func() { _ = held.Close() }, nil +} + +func coalescePar(flag, cfg int) int { + if flag > 0 { + return flag + } + return cfg +} + +// loadRunConfig finds and loads the prenup config, defaulting to repoRoot if no +// explicit path was provided. +func loadRunConfig(repoRoot, cfgPath string) (config.Config, error) { + if cfgPath == "" { + p, err := config.Find(repoRoot) + if err != nil { + return config.Config{}, err + } + if p == "" { + return config.Config{}, fmt.Errorf("no .prenup.yaml found in %s; run `prenup init`", repoRoot) + } + cfgPath = p + } + return config.Load(cfgPath, repoRoot) +} + +// discoverOutcome describes the result of change/module discovery. +// +// - done == false: changedFiles + modules describe real work to do +// - done == true: the caller should print message (if any) and exit 0 +type discoverOutcome struct { + changedFiles []string + modules []string + done bool + message string +} + +// discoverChangesAndModules returns the changed files and modules to run +// against, or signals that there's nothing to do. +func discoverChangesAndModules(gitRunner *git.Runner, repoRoot string, cfg config.Config, all bool) (discoverOutcome, error) { + var out discoverOutcome + if !all { + files, err := discover.ChangedFiles(gitRunner, cfg.Exclude) + if err != nil { + return discoverOutcome{}, err + } + out.changedFiles = files + if len(files) == 0 { + return discoverOutcome{done: true, message: "No relevant files changed. Skipping Prenup."}, nil + } + } + + out.modules = discover.Modules(repoRoot, out.changedFiles, cfg.ModuleMarkers) + if all || (len(out.modules) == 0 && len(out.changedFiles) > 0) { + // --all or no modules detected: feed the plan a synthetic "." module + // and let per-task path filters do the work. + out.modules = []string{"."} + } + if len(out.modules) == 0 { + return discoverOutcome{done: true, message: "No changed modules detected. Skipping Prenup."}, nil + } + return out, nil +} + +// selectionOutcome describes the result of resolving which tasks to run. +// +// - done == false: selected is the (possibly nil) selection map; nil means +// "use the config's default_selected" +// - done == true: print message (if any), exit with exitCode +type selectionOutcome struct { + selected map[string]bool + done bool + message string + exitCode int +} + +// resolveSelection returns the selected task set, either from --task flags, +// the human selection UI, or nil (meaning the runner uses default_selected). +func resolveSelection(opts runOptions, mode config.OutputMode, modules []string, cfg config.Config, + verInfo versionInfo) (selectionOutcome, error) { + if len(opts.taskNames) > 0 { + selected := make(map[string]bool, len(opts.taskNames)) + for _, n := range opts.taskNames { + selected[n] = true + } + return selectionOutcome{selected: selected}, nil + } + if mode != config.OutputHuman || opts.noInteractive { + return selectionOutcome{}, nil + } + res, err := human.SelectTasks(human.SelectionInput{ + Version: verInfo.version, + Notice: verInfo.notice, + Modules: modules, + Tasks: cfg.Tasks, + }) + if errors.Is(err, human.ErrCanceled) { + return selectionOutcome{done: true, exitCode: 1, message: "canceled"}, nil + } + if err != nil { + return selectionOutcome{}, err + } + if res.Skipped { + return selectionOutcome{done: true, message: "Skipping tasks as requested."}, nil + } + if len(res.Selected) == 0 { + return selectionOutcome{done: true, message: "No tasks selected. Committing without running checks."}, nil + } + return selectionOutcome{selected: res.Selected}, nil +} + +// versionInfo carries the resolved version and optional update warning. +type versionInfo struct { + version string + notice string +} + +func maybeVersionCheck(ctx context.Context) versionInfo { + ver := ResolvedVersion() + info := versionInfo{version: ver} + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + res := versioncheck.Check(ctx, ver, githubTokenForVersionCheck()) + if res.Error == nil && res.IsOutdated { + info.notice = fmt.Sprintf( + "Update available %s - run `go install github.com/c2fo/prenup/cmd/prenup@latest`", + res.LatestVersion) + } + return info +} + +func githubTokenForVersionCheck() string { + for _, key := range []string{"PRENUP_GITHUB_TOKEN", "GITHUB_TOKEN", "GH_TOKEN"} { + if v := strings.TrimSpace(os.Getenv(key)); v != "" { + return v + } + } + return "" +} + +// runWithMode wires the chosen output sink to the runner and blocks until the +// run completes. +func runWithMode(ctx context.Context, mode config.OutputMode, plan runner.Plan, opts runner.Options) (runner.Result, error) { + switch mode { + case config.OutputJSON: + sink := jsonout.New(os.Stdout) + opts.Sink = sink + return runner.Run(ctx, plan, opts) + case config.OutputMarkdown: + sink := markdown.New(os.Stdout) + opts.Sink = sink + return runner.Run(ctx, plan, opts) + case config.OutputHuman: + return runHumanTUI(ctx, plan, opts) + default: + sink := markdown.New(os.Stdout) + opts.Sink = sink + return runner.Run(ctx, plan, opts) + } +} + +// runHumanTUI wires the Bubble Tea runner UI and the summary printer. +// +// Lifecycle: +// - the runner runs in a goroutine that emits into channelSink +// - the TUI runs on the main goroutine and consumes events +// - when the runner returns we Close the sink so any late deferred-cleanup +// events (stash pop notices, etc.) drop instead of pinning the goroutine +// - if the TUI itself errors early (terminal init, etc.), we still Close +// and cancel the context so the runner unblocks and tears down +func runHumanTUI(ctx context.Context, plan runner.Plan, opts runner.Options) (runner.Result, error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + channelSink := human.NewEventChannelSink() + summarySink := human.NewSummarySink(os.Stdout) + opts.Sink = runner.NewMultiSink(channelSink, summarySink) + + model := human.NewRunnerModel(channelSink) + prog := tea.NewProgram(model, tea.WithAltScreen(), tea.WithOutput(os.Stdout)) + + type runOutcome struct { + result runner.Result + err error + } + runDone := make(chan runOutcome, 1) + go func() { + result, err := runner.Run(ctx, plan, opts) + _ = channelSink.Close() + runDone <- runOutcome{result: result, err: err} + }() + + uiErr := func() error { + _, err := prog.Run() + return err + }() + + // Either way (TUI exited cleanly or errored), tell the runner we're done + // listening; Emit will start dropping immediately. + cancel() + _ = channelSink.Close() + out := <-runDone + _ = summarySink.Close() + + if uiErr != nil { + return out.result, fmt.Errorf("runner UI: %w", uiErr) + } + if out.err != nil { + return out.result, out.err + } + return out.result, nil +} diff --git a/internal/cli/run_internal_test.go b/internal/cli/run_internal_test.go new file mode 100644 index 0000000..92ba185 --- /dev/null +++ b/internal/cli/run_internal_test.go @@ -0,0 +1,189 @@ +package cli + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/git" +) + +// RunDiscoverTestSuite groups tests for discoverChangesAndModules. +// They share a non-trivial fixture (a freshly-initialized git repo +// with a single seed commit), so a SetupTest pays for itself here. +// +// Other run_internal helpers (coalescePar, resolveSelection, +// loadRunConfig) have no shared fixture and remain flat tests below. +type RunDiscoverTestSuite struct { + suite.Suite + repo string // absolute path to the temp git repo + cfg config.Config +} + +func (s *RunDiscoverTestSuite) SetupTest() { + dir := s.T().TempDir() + for _, args := range [][]string{ + {"init", "-b", "main"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "test"}, + // Disable GPG signing so the seed commit succeeds in + // environments where the dev's global git requires a key. + {"config", "commit.gpgsign", "false"}, + } { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed git binary, internal args. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git %s: %s", strings.Join(args, " "), string(out)) + } + s.Require().NoError(os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi\n"), 0o600)) + for _, args := range [][]string{{"add", "README.md"}, {"commit", "-m", "init"}} { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed git binary, internal args. + cmd.Dir = dir + s.Require().NoError(cmd.Run()) + } + s.repo = dir + s.cfg = config.DefaultConfig() +} + +// TestNoChanges returns done with a friendly message when the worktree is clean. +func (s *RunDiscoverTestSuite) TestNoChanges() { + out, err := discoverChangesAndModules(git.New(s.repo), s.repo, s.cfg, false) + s.Require().NoError(err) + s.True(out.done) + s.Contains(out.message, "No relevant files changed") +} + +// TestAllFlagSynthesizesDot pins that --all forces a "." module even +// against a clean tree, so an operator can re-run all tasks at will. +func (s *RunDiscoverTestSuite) TestAllFlagSynthesizesDot() { + out, err := discoverChangesAndModules(git.New(s.repo), s.repo, s.cfg, true) + s.Require().NoError(err) + s.False(out.done) + s.Equal([]string{"."}, out.modules) +} + +// TestUntrackedFileCountsAsChange pins that an untracked file is +// treated as a real change (not just modifications), and that without +// module markers the runner falls back to a single "." module. +func (s *RunDiscoverTestSuite) TestUntrackedFileCountsAsChange() { + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "added.go"), []byte("package x\n"), 0o600)) + + out, err := discoverChangesAndModules(git.New(s.repo), s.repo, s.cfg, false) + s.Require().NoError(err) + s.False(out.done) + s.Contains(out.changedFiles, "added.go") + s.Equal([]string{"."}, out.modules) +} + +func TestRunDiscoverSuite(t *testing.T) { + suite.Run(t, new(RunDiscoverTestSuite)) +} + +// --- standalone tests below: pure helpers with no shared fixture, so +// a suite would be ceremony without payoff. They stay flat and use +// assert.New / require.New per the testify house style. + +// TestCoalescePar verifies the precedence rule: an explicit --parallelism +// flag wins over the config value, and 0 falls through to the config. +func TestCoalescePar(t *testing.T) { + t.Parallel() + cases := []struct { + name string + flag int + cfg int + want int + }{ + {"flag wins when set", 4, 8, 4}, + {"flag zero falls back to cfg", 0, 8, 8}, + {"both zero stays zero", 0, 0, 0}, + {"negative flag treated as unset", -1, 8, 8}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, coalescePar(tc.flag, tc.cfg)) + }) + } +} + +// TestResolveSelectionExplicitTaskFlags returns exactly the requested set +// regardless of mode. +func TestResolveSelectionExplicitTaskFlags(t *testing.T) { + t.Parallel() + is := assert.New(t) + must := require.New(t) + + opts := runOptions{taskNames: []string{"lint", "test"}} + cfg := config.DefaultConfig() + out, err := resolveSelection(opts, config.OutputJSON, []string{"."}, cfg, versionInfo{}) + must.NoError(err) + is.False(out.done) + is.Equal(map[string]bool{"lint": true, "test": true}, out.selected) +} + +// TestResolveSelectionNonHumanReturnsNil exercises the fast-path: in +// machine-readable modes there is no interactive picker, so the runner +// should fall back to default_selected (signaled by selected == nil). +func TestResolveSelectionNonHumanReturnsNil(t *testing.T) { + t.Parallel() + is := assert.New(t) + must := require.New(t) + + opts := runOptions{} + cfg := config.DefaultConfig() + out, err := resolveSelection(opts, config.OutputJSON, []string{"."}, cfg, versionInfo{}) + must.NoError(err) + is.False(out.done) + is.Nil(out.selected) +} + +// TestResolveSelectionNoInteractiveReturnsNil ensures --no-interactive on the +// human path also short-circuits to "use defaults". +func TestResolveSelectionNoInteractiveReturnsNil(t *testing.T) { + t.Parallel() + is := assert.New(t) + must := require.New(t) + + opts := runOptions{noInteractive: true} + cfg := config.DefaultConfig() + out, err := resolveSelection(opts, config.OutputHuman, []string{"."}, cfg, versionInfo{}) + must.NoError(err) + is.False(out.done) + is.Nil(out.selected) +} + +// TestLoadRunConfigMissing surfaces a useful error when no config exists. +func TestLoadRunConfigMissing(t *testing.T) { + t.Parallel() + is := assert.New(t) + must := require.New(t) + + _, err := loadRunConfig(t.TempDir(), "") + must.Error(err) + is.Contains(err.Error(), "no .prenup.yaml found") + is.Contains(err.Error(), "prenup init") +} + +// TestLoadRunConfigExplicitPath honors an absolute path argument and ignores +// repo-root discovery. +func TestLoadRunConfigExplicitPath(t *testing.T) { + t.Parallel() + is := assert.New(t) + must := require.New(t) + + dir := t.TempDir() + path := filepath.Join(dir, "custom.yaml") + must.NoError(os.WriteFile(path, []byte("version: 2\ntasks:\n - name: t\n command: \"true\"\n"), 0o600)) + + cfg, err := loadRunConfig(dir, path) + must.NoError(err) + is.Equal(2, cfg.Version) + is.Len(cfg.Tasks, 1) +} diff --git a/internal/cli/version.go b/internal/cli/version.go new file mode 100644 index 0000000..34dce0e --- /dev/null +++ b/internal/cli/version.go @@ -0,0 +1,40 @@ +package cli + +import ( + "runtime/debug" + "sync" +) + +// Version is the release version, set at link time with -X. +// When unset, debug.ReadBuildInfo() is consulted (for `go install ...@v1.2.3`). +var Version = "dev" + +// resolvedVersion memoizes the version lookup so repeated calls (UI render, +// version check, summary) don't re-read build info on every access. Tests +// that mutate Version should call ResetVersionCacheForTest first. +var resolvedVersion = sync.OnceValue(func() string { + if Version != "dev" { + return Version + } + if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" && info.Main.Version != "(devel)" { + return info.Main.Version + } + return Version +}) + +// ResolvedVersion returns the string displayed in UIs and used for update checks. +func ResolvedVersion() string { return resolvedVersion() } + +// ResetVersionCacheForTest clears the memoized version so tests can swap +// Version and observe the change. Not for production use. +func ResetVersionCacheForTest() { + resolvedVersion = sync.OnceValue(func() string { + if Version != "dev" { + return Version + } + if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" && info.Main.Version != "(devel)" { + return info.Main.Version + } + return Version + }) +} diff --git a/internal/cli/versioncheck_test.go b/internal/cli/versioncheck_test.go new file mode 100644 index 0000000..1f2f561 --- /dev/null +++ b/internal/cli/versioncheck_test.go @@ -0,0 +1,29 @@ +package cli + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// TestMaybeVersionCheckRespectsCancelledContext is a smoke test that the +// version check returns promptly when the parent context is already +// canceled, instead of blocking on the network call until the inner 10s +// timeout fires. This guards against future regressions that would freeze +// `prenup run` for ten seconds when the user has hit Ctrl-C. +func TestMaybeVersionCheckRespectsCancelledContext(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + cancel() // cancel before invocation; the HTTP call should fail immediately + + start := time.Now() + info := maybeVersionCheck(ctx) + elapsed := time.Since(start) + + assert.NotEmpty(t, info.version, "should still report the resolved version") + assert.Empty(t, info.notice, "no update notice when the network call failed") + // 2s is a very loose ceiling; in practice this returns sub-second. + assert.Less(t, elapsed, 2*time.Second, "must not block on the 10s inner timeout") +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..97191c7 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,300 @@ +// Package config defines the v2 .prenup.yaml schema, loading, defaults, and validation. +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/bmatcuk/doublestar/v4" + "gopkg.in/yaml.v3" +) + +// ConfigFilenames lists the filenames prenup looks for in the repository root, +// in order of preference. The first one found is used. +var ConfigFilenames = []string{".prenup.yaml", ".prenup.yml"} + +// OutputMode controls how prenup renders progress and results. +type OutputMode string + +const ( + // OutputAuto selects human for TTYs, markdown otherwise. + OutputAuto OutputMode = "auto" + // OutputHuman is the Bubble Tea interactive UI. + OutputHuman OutputMode = "human" + // OutputMarkdown is a plain-text streaming + final markdown digest mode. + OutputMarkdown OutputMode = "markdown" + // OutputJSON is an NDJSON event stream mode for agents. + OutputJSON OutputMode = "json" +) + +// Config is the parsed v2 configuration. +type Config struct { + Version int `yaml:"version"` + ModuleMarkers []string `yaml:"module_markers,omitempty"` + Exclude []string `yaml:"exclude,omitempty"` + CleanWorktree *bool `yaml:"clean_worktree,omitempty"` + MaxParallelism int `yaml:"max_parallelism,omitempty"` + Output OutputMode `yaml:"output,omitempty"` + Tasks []Task `yaml:"tasks"` + + // Path is the absolute path the config was loaded from. Not serialized. + Path string `yaml:"-"` +} + +// Task is a single entry in the tasks list. +type Task struct { + Name string `yaml:"name"` + DefaultSelected bool `yaml:"default_selected,omitempty"` + Command string `yaml:"command"` + PerModule bool `yaml:"per_module,omitempty"` + WorkingDir string `yaml:"working_dir,omitempty"` + Paths []string `yaml:"paths,omitempty"` + PathsIgnore []string `yaml:"paths_ignore,omitempty"` + OutputPatterns []string `yaml:"output_patterns,omitempty"` + StageOutput bool `yaml:"stage_output,omitempty"` + Parallel *bool `yaml:"parallel,omitempty"` + CleanWorktree *bool `yaml:"clean_worktree,omitempty"` + Env map[string]string `yaml:"env,omitempty"` +} + +// CleanWorktreeEnabled returns the effective clean_worktree value for this task +// given the config-level default. +func (t Task) CleanWorktreeEnabled(cfgDefault bool) bool { + if t.CleanWorktree != nil { + return *t.CleanWorktree + } + return cfgDefault +} + +// ParallelEnabled returns whether per-module runs of this task may fan out. +// Defaults to true for per_module tasks, false otherwise. +func (t Task) ParallelEnabled() bool { + if t.Parallel != nil { + return *t.Parallel + } + return t.PerModule +} + +// DefaultConfig returns a Config populated with all defaults applied. +// Callers then overlay user-provided YAML on top. +func DefaultConfig() Config { + trueVal := true + return Config{ + Version: 2, + ModuleMarkers: []string{"go.mod"}, + CleanWorktree: &trueVal, + MaxParallelism: 0, // 0 == NumCPU at runtime + Output: OutputAuto, + } +} + +// CleanWorktreeEnabled returns the effective config-level default for stashing. +func (c Config) CleanWorktreeEnabled() bool { + if c.CleanWorktree == nil { + return true + } + return *c.CleanWorktree +} + +// Find returns the absolute path to the first config file found under repoRoot. +// Returns an empty string and nil error if no config exists. +func Find(repoRoot string) (string, error) { + for _, name := range ConfigFilenames { + p := filepath.Join(repoRoot, name) + info, err := os.Stat(p) + if err == nil && !info.IsDir() { + return p, nil + } + if err != nil && !os.IsNotExist(err) { + return "", fmt.Errorf("checking %s: %w", p, err) + } + } + return "", nil +} + +// Load parses the YAML file at path and returns a fully-defaulted, validated Config. +// The path must be inside repoRoot. +func Load(path, repoRoot string) (Config, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return Config{}, fmt.Errorf("resolving config path: %w", err) + } + absRepo, err := filepath.Abs(repoRoot) + if err != nil { + return Config{}, fmt.Errorf("resolving repo root: %w", err) + } + // Resolve symlinks on both sides before comparing. A purely + // lexical check would let a symlink that lives inside the repo + // but targets a file outside the repo slip through and be read, + // which would defeat the "config must be inside the repo" rule. + resolvedPath, err := filepath.EvalSymlinks(absPath) + if err != nil { + return Config{}, fmt.Errorf("resolving config path: %w", err) + } + resolvedRepo, err := filepath.EvalSymlinks(absRepo) + if err != nil { + return Config{}, fmt.Errorf("resolving repo root: %w", err) + } + rel, err := filepath.Rel(resolvedRepo, resolvedPath) + if err != nil { + return Config{}, fmt.Errorf("checking config path: %w", err) + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return Config{}, errors.New("config file path is outside the repository") + } + + // resolvedPath is validated above to live under the repository root. + data, err := os.ReadFile(resolvedPath) + if err != nil { + return Config{}, fmt.Errorf("reading config: %w", err) + } + + return Parse(data, resolvedPath) +} + +// Parse decodes YAML bytes into a Config, applies defaults, and validates. +// pathForErrors is used only in error messages. +func Parse(data []byte, pathForErrors string) (Config, error) { + // Start from a zero Config so we can detect a missing `version:` key; the + // user-provided YAML is then overlaid and defaults applied afterwards. + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + // Common footgun: `version: "2"` (string instead of int). + if hint := versionTypeHint(data); hint != "" { + return Config{}, fmt.Errorf("parsing %s: %s (underlying error: %w)", pathForErrors, hint, err) + } + return Config{}, fmt.Errorf("parsing %s: %w", pathForErrors, err) + } + + if cfg.Version == 0 { + // Missing `version:` is treated as v1 and rejected to force users + // through `prenup migrate`. + return Config{}, errors.New(`config is missing "version: 2". If this is a v1 config, run "prenup migrate"`) + } + + // Apply defaults for fields YAML unmarshal would leave at zero. + defaults := DefaultConfig() + if cfg.CleanWorktree == nil { + cfg.CleanWorktree = defaults.CleanWorktree + } + if len(cfg.ModuleMarkers) == 0 { + cfg.ModuleMarkers = []string{"go.mod"} + } + if cfg.Output == "" { + cfg.Output = OutputAuto + } + cfg.Path = pathForErrors + + if err := Validate(cfg); err != nil { + return Config{}, err + } + return cfg, nil +} + +// Validate performs structural checks on cfg and returns a combined error. +func Validate(cfg Config) error { + errs := validateTopLevel(cfg) + seen := make(map[string]bool) + for i := range cfg.Tasks { + errs = append(errs, validateTask(&cfg.Tasks[i], i, seen)...) + } + if len(errs) == 0 { + return nil + } + return fmt.Errorf("invalid config %s:\n - %s", cfg.Path, strings.Join(errs, "\n - ")) +} + +// validateTopLevel checks all non-task fields. +func validateTopLevel(cfg Config) []string { + var errs []string + if cfg.Version != 2 { + errs = append(errs, fmt.Sprintf("unsupported config version %d (expected 2)", cfg.Version)) + } + switch cfg.Output { + case OutputAuto, OutputHuman, OutputMarkdown, OutputJSON, "": + default: + errs = append(errs, fmt.Sprintf("unknown output mode %q (want auto, human, markdown, or json)", cfg.Output)) + } + if cfg.MaxParallelism < 0 { + errs = append(errs, fmt.Sprintf("max_parallelism must be >= 0, got %d", cfg.MaxParallelism)) + } + if len(cfg.Tasks) == 0 { + errs = append(errs, "no tasks defined") + } + for _, p := range cfg.Exclude { + if err := validatePattern(p); err != nil { + errs = append(errs, "exclude: "+err.Error()) + } + } + return errs +} + +// validateTask checks a single task entry. seen accumulates names across +// calls so duplicates can be flagged. +func validateTask(t *Task, idx int, seen map[string]bool) []string { + var errs []string + label := fmt.Sprintf("tasks[%d]", idx) + switch { + case t.Name == "": + errs = append(errs, label+": name is required") + case seen[t.Name]: + errs = append(errs, fmt.Sprintf("%s: duplicate task name %q", label, t.Name)) + default: + seen[t.Name] = true + } + if strings.TrimSpace(t.Command) == "" { + errs = append(errs, fmt.Sprintf("%s (%s): command is required", label, t.Name)) + } + if t.StageOutput && len(t.OutputPatterns) == 0 { + errs = append(errs, fmt.Sprintf("%s (%s): stage_output: true requires output_patterns", label, t.Name)) + } + errs = append(errs, validateTaskPatterns(label, t)...) + return errs +} + +// validateTaskPatterns runs validatePattern over every pattern field on t. +func validateTaskPatterns(label string, t *Task) []string { + var errs []string + for _, group := range []struct { + field string + patterns []string + }{ + {"paths", t.Paths}, + {"paths_ignore", t.PathsIgnore}, + {"output_patterns", t.OutputPatterns}, + } { + for _, p := range group.patterns { + if err := validatePattern(p); err != nil { + errs = append(errs, fmt.Sprintf("%s (%s) %s: %s", label, t.Name, group.field, err)) + } + } + } + return errs +} + +// validatePattern returns an error if the doublestar pattern is malformed. +func validatePattern(p string) error { + if _, err := doublestar.Match(p, ""); err != nil { + return fmt.Errorf("invalid pattern %q: %w", p, err) + } + return nil +} + +// versionTypeHint returns a friendly hint when YAML decoding fails because +// `version:` was a string instead of an int. Returns "" if the source does +// not contain that pattern. +func versionTypeHint(data []byte) string { + var probe struct { + Version yaml.Node `yaml:"version"` + } + if err := yaml.Unmarshal(data, &probe); err != nil { + return "" + } + if probe.Version.Kind == yaml.ScalarNode && probe.Version.Tag == "!!str" { + return `"version" must be an integer (use "version: 2", not "version: \"2\"")` + } + return "" +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..27752fb --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,147 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseValid(t *testing.T) { + data := []byte(` +version: 2 +module_markers: + - go.mod +exclude: + - "**/*.yaml" +clean_worktree: false +max_parallelism: 4 +output: json +tasks: + - name: "Tests" + command: "go test ./..." + default_selected: true + per_module: true + paths: ["**/*.go"] + - name: "Docs" + command: "make docs" + stage_output: true + output_patterns: ["docs/**/*.md"] +`) + cfg, err := Parse(data, "/tmp/test.yaml") + require.NoError(t, err) + + assert.Equal(t, 2, cfg.Version) + assert.Equal(t, []string{"go.mod"}, cfg.ModuleMarkers) + assert.Equal(t, 4, cfg.MaxParallelism) + assert.Equal(t, OutputJSON, cfg.Output) + require.NotNil(t, cfg.CleanWorktree) + assert.False(t, *cfg.CleanWorktree) + assert.False(t, cfg.CleanWorktreeEnabled()) + + require.Len(t, cfg.Tasks, 2) + assert.True(t, cfg.Tasks[0].DefaultSelected) + assert.True(t, cfg.Tasks[0].PerModule) + assert.True(t, cfg.Tasks[0].ParallelEnabled(), "per-module task should default parallel=true") + assert.False(t, cfg.Tasks[1].ParallelEnabled(), "non-per-module task should default parallel=false") +} + +func TestParseMissingVersionRejected(t *testing.T) { + data := []byte(` +tasks: + - name: foo + command: "true" +`) + _, err := Parse(data, "/tmp/test.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), `missing "version: 2"`) +} + +func TestValidateRejectsDuplicateTaskName(t *testing.T) { + data := []byte(` +version: 2 +tasks: + - name: foo + command: "true" + - name: foo + command: "true" +`) + _, err := Parse(data, "/tmp/test.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), "duplicate task name") +} + +func TestValidateRequiresOutputPatternsForStageOutput(t *testing.T) { + data := []byte(` +version: 2 +tasks: + - name: foo + command: "true" + stage_output: true +`) + _, err := Parse(data, "/tmp/test.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), "stage_output: true requires output_patterns") +} + +func TestValidateRejectsUnknownOutputMode(t *testing.T) { + data := []byte(` +version: 2 +output: flashy +tasks: + - name: foo + command: "true" +`) + _, err := Parse(data, "/tmp/test.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown output mode") +} + +func TestFindPrefersYamlOverYml(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte("version: 2\n"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yml"), []byte("version: 2\n"), 0o600)) + + p, err := Find(dir) + require.NoError(t, err) + assert.Equal(t, filepath.Join(dir, ".prenup.yaml"), p) +} + +func TestLoadRejectsPathOutsideRepo(t *testing.T) { + repo := t.TempDir() + other := t.TempDir() + cfgPath := filepath.Join(other, ".prenup.yaml") + require.NoError(t, os.WriteFile(cfgPath, []byte("version: 2\ntasks: [{name: f, command: \"true\"}]\n"), 0o600)) + + _, err := Load(cfgPath, repo) + require.Error(t, err) + assert.Contains(t, err.Error(), "outside the repository") +} + +// TestLoadRejectsSymlinkEscapingRepo guards against a symlink that +// lives inside the repo but points at a config file outside the repo. +// A purely lexical check on the user-supplied path would let such a +// file be read; Load must EvalSymlinks both sides before comparing. +func TestLoadRejectsSymlinkEscapingRepo(t *testing.T) { + repo := t.TempDir() + other := t.TempDir() + + target := filepath.Join(other, ".prenup.yaml") + require.NoError(t, os.WriteFile(target, []byte("version: 2\ntasks: [{name: f, command: \"true\"}]\n"), 0o600)) + + link := filepath.Join(repo, ".prenup.yaml") + if err := os.Symlink(target, link); err != nil { + t.Skipf("symlinks unsupported on this filesystem: %v", err) + } + + _, err := Load(link, repo) + require.Error(t, err) + assert.Contains(t, err.Error(), "outside the repository") +} + +func TestDefaultConfigStashOn(t *testing.T) { + cfg := DefaultConfig() + assert.True(t, cfg.CleanWorktreeEnabled()) +} diff --git a/internal/config/migrate.go b/internal/config/migrate.go new file mode 100644 index 0000000..952cc15 --- /dev/null +++ b/internal/config/migrate.go @@ -0,0 +1,76 @@ +package config + +import ( + "bytes" + "fmt" + + "gopkg.in/yaml.v3" +) + +// V1Config matches the v1 YAML shape so we can read existing .prenup.yml files +// and rewrite them as v2. +type V1Config struct { + Exclude []string `yaml:"exclude"` + Tasks []V1Task `yaml:"tasks"` +} + +// V1Task is the v1 task entry. +type V1Task struct { + Name string `yaml:"name"` + DefaultSelected bool `yaml:"default_selected"` + PerModule bool `yaml:"per_module"` + Command string `yaml:"command,omitempty"` + WorkingDir string `yaml:"working_dir,omitempty"` + StageOutput bool `yaml:"stage_output,omitempty"` + OutputPatterns []string `yaml:"output_patterns,omitempty"` +} + +// MigrateV1 converts v1 YAML bytes into a v2 Config and returns freshly +// marshaled YAML bytes. The returned Config is also populated for callers that +// want to inspect it directly. +func MigrateV1(data []byte) (Config, []byte, error) { + var v1 V1Config + if err := yaml.Unmarshal(data, &v1); err != nil { + return Config{}, nil, fmt.Errorf("parsing v1 config: %w", err) + } + + cfg := DefaultConfig() + cfg.Exclude = v1.Exclude + cfg.Tasks = make([]Task, 0, len(v1.Tasks)) + for _, t := range v1.Tasks { + cfg.Tasks = append(cfg.Tasks, Task{ + Name: t.Name, + DefaultSelected: t.DefaultSelected, + Command: t.Command, + PerModule: t.PerModule, + WorkingDir: t.WorkingDir, + OutputPatterns: t.OutputPatterns, + StageOutput: t.StageOutput, + }) + } + + // Omit the default CleanWorktree pointer from the marshaled output unless + // the user has opinions; defaults are applied at load time. + cfg.CleanWorktree = nil + + out, err := Marshal(cfg) + if err != nil { + return Config{}, nil, err + } + return cfg, out, nil +} + +// Marshal writes a v2 config back to YAML with a stable, commented layout. +func Marshal(cfg Config) ([]byte, error) { + var buf bytes.Buffer + buf.WriteString("# Prenup configuration. See https://github.com/c2fo/prenup for docs.\n") + enc := yaml.NewEncoder(&buf) + enc.SetIndent(2) + if err := enc.Encode(cfg); err != nil { + return nil, fmt.Errorf("encoding config: %w", err) + } + if err := enc.Close(); err != nil { + return nil, fmt.Errorf("closing encoder: %w", err) + } + return buf.Bytes(), nil +} diff --git a/internal/config/migrate_test.go b/internal/config/migrate_test.go new file mode 100644 index 0000000..9d274cd --- /dev/null +++ b/internal/config/migrate_test.go @@ -0,0 +1,56 @@ +package config + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// v1 example taken from the pre-v2 prenup.example.yaml. +const v1ConfigExample = ` +exclude: + - ".github" + - "*.yaml" + +tasks: + - name: "Run tests" + default_selected: true + command: "go test ./..." + per_module: true + + - name: "Generate CLI docs" + default_selected: true + per_module: true + command: "make docs" + working_dir: "{{.repo_root}}" + output_patterns: + - "doc/cmd/*.md" + stage_output: true +` + +func TestMigrateV1Preserves(t *testing.T) { + cfg, out, err := MigrateV1([]byte(v1ConfigExample)) + require.NoError(t, err) + + assert.Equal(t, 2, cfg.Version) + require.Len(t, cfg.Tasks, 2) + assert.Equal(t, "Run tests", cfg.Tasks[0].Name) + assert.True(t, cfg.Tasks[0].PerModule) + assert.True(t, cfg.Tasks[1].StageOutput) + + assert.True(t, strings.HasPrefix(string(out), "# Prenup configuration.")) + assert.Contains(t, string(out), "version: 2") + assert.Contains(t, string(out), "Run tests") +} + +func TestMigrateThenParseRoundTrips(t *testing.T) { + _, out, err := MigrateV1([]byte(v1ConfigExample)) + require.NoError(t, err) + + reparsed, err := Parse(out, "migrated") + require.NoError(t, err) + require.Len(t, reparsed.Tasks, 2) + assert.Equal(t, "Run tests", reparsed.Tasks[0].Name) +} diff --git a/internal/config/schema.go b/internal/config/schema.go new file mode 100644 index 0000000..8fd06fc --- /dev/null +++ b/internal/config/schema.go @@ -0,0 +1,12 @@ +package config + +import _ "embed" + +//go:embed schema.json +var embeddedSchema []byte + +// Schema returns the embedded JSON schema describing the v2 config. +// The bytes are stable across calls; callers must not mutate them. +func Schema() []byte { + return embeddedSchema +} diff --git a/internal/config/schema.json b/internal/config/schema.json new file mode 100644 index 0000000..31ef3a1 --- /dev/null +++ b/internal/config/schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/c2fo/prenup/main/assets/prenup.schema.json", + "title": "Prenup v2 configuration", + "$comment": "This file is the canonical source. assets/prenup.schema.json must stay byte-identical. When adding a v3, replace `version.const: 2` with a `oneOf` over per-version schemas.", + "type": "object", + "required": ["version", "tasks"], + "additionalProperties": false, + "properties": { + "version": { + "type": "integer", + "const": 2, + "description": "Config schema version. Must be 2." + }, + "module_markers": { + "type": "array", + "items": { "type": "string" }, + "description": "File names whose presence marks a module directory. Defaults to ['go.mod']." + }, + "exclude": { + "type": "array", + "items": { "type": "string" }, + "description": "Doublestar glob patterns of files to ignore for change detection." + }, + "clean_worktree": { + "type": "boolean", + "description": "Stash unstaged changes around task execution so checks see exactly the pending commit. Default true." + }, + "max_parallelism": { + "type": "integer", + "minimum": 0, + "description": "Max concurrent per-module task runs. 0 means NumCPU." + }, + "output": { + "type": "string", + "enum": ["auto", "human", "markdown", "json"], + "description": "Default output mode. Overridable via --output." + }, + "tasks": { + "type": "array", + "items": { "$ref": "#/$defs/task" } + } + }, + "$defs": { + "task": { + "type": "object", + "required": ["name", "command"], + "additionalProperties": false, + "properties": { + "name": { "type": "string", "minLength": 1 }, + "default_selected": { "type": "boolean" }, + "command": { "type": "string", "minLength": 1 }, + "per_module": { "type": "boolean" }, + "working_dir": { "type": "string" }, + "paths": { "type": "array", "items": { "type": "string" } }, + "paths_ignore": { "type": "array", "items": { "type": "string" } }, + "output_patterns": { "type": "array", "items": { "type": "string" } }, + "stage_output": { "type": "boolean" }, + "parallel": { "type": "boolean" }, + "clean_worktree": { "type": "boolean" }, + "env": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } + } + } +} diff --git a/internal/config/schema_test.go b/internal/config/schema_test.go new file mode 100644 index 0000000..bd7aa73 --- /dev/null +++ b/internal/config/schema_test.go @@ -0,0 +1,25 @@ +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestSchemaAssetMatchesEmbedded guards against the two schema copies (the +// embedded one in this package and the public asset under ../../assets) +// drifting apart. They are duplicated so external consumers can fetch the +// canonical $id URL without pulling in the full Go module; the duplication +// is acceptable as long as a CI signal catches divergence immediately. +func TestSchemaAssetMatchesEmbedded(t *testing.T) { + t.Parallel() + wd, err := os.Getwd() + require.NoError(t, err) + assetPath := filepath.Join(wd, "..", "..", "assets", "prenup.schema.json") + asset, err := os.ReadFile(assetPath) //nolint:gosec // G304: path is built from a fixed filename relative to the test wd. + require.NoError(t, err, "read public asset schema") + require.Equal(t, string(asset), string(Schema()), + "assets/prenup.schema.json and internal/config/schema.json must be byte-identical; update both when the schema changes") +} diff --git a/internal/discover/changes.go b/internal/discover/changes.go new file mode 100644 index 0000000..70e4384 --- /dev/null +++ b/internal/discover/changes.go @@ -0,0 +1,98 @@ +// Package discover finds what changed in the working tree and which modules own +// those changes. It also applies the repo-level exclude filter and per-task +// path filters. +package discover + +import ( + "fmt" + + "github.com/bmatcuk/doublestar/v4" + + "github.com/c2fo/prenup/internal/git" +) + +// ChangedFiles returns deduplicated paths (relative to repo root) that are +// staged, unstaged, or untracked. Paths matching any excludePattern (a +// doublestar glob) are dropped. +func ChangedFiles(r *git.Runner, excludePatterns []string) ([]string, error) { + staged, err := r.StagedFiles() + if err != nil { + return nil, fmt.Errorf("staged files: %w", err) + } + unstaged, err := r.UnstagedFiles() + if err != nil { + return nil, fmt.Errorf("unstaged files: %w", err) + } + untracked, err := r.UntrackedFiles() + if err != nil { + return nil, fmt.Errorf("untracked files: %w", err) + } + + seen := make(map[string]struct{}) + all := make([]string, 0, len(staged)+len(unstaged)+len(untracked)) + for _, src := range [][]string{staged, unstaged, untracked} { + for _, f := range src { + if _, ok := seen[f]; ok { + continue + } + seen[f] = struct{}{} + all = append(all, f) + } + } + + if len(excludePatterns) == 0 { + return all, nil + } + + kept := make([]string, 0, len(all)) + for _, f := range all { + if Matches(excludePatterns, f) { + continue + } + kept = append(kept, f) + } + return kept, nil +} + +// Matches returns true when path matches any doublestar pattern. +// +// Pattern errors are intentionally swallowed: config validation should have +// caught them at load time, and a per-event log here would amount to one +// warning per file per pattern. Use ValidatePatterns at config-load to +// surface bad patterns early. +func Matches(patterns []string, path string) bool { + for _, p := range patterns { + if ok, err := doublestar.Match(p, path); err == nil && ok { + return true + } + } + return false +} + +// ValidatePatterns returns the first invalid pattern (if any) along with the +// underlying parser error. Used by config.Validate to fail loudly at load +// time instead of letting Matches silently miss matches. +func ValidatePatterns(patterns []string) error { + for _, p := range patterns { + if _, err := doublestar.Match(p, ""); err != nil { + return fmt.Errorf("invalid pattern %q: %w", p, err) + } + } + return nil +} + +// FilterByPaths returns only the files matching include (if non-empty) and +// not matching exclude. An empty include list includes everything. +func FilterByPaths(files, include, exclude []string) []string { + out := make([]string, 0, len(files)) + for _, f := range files { + if len(include) > 0 && !Matches(include, f) { + continue + } + if len(exclude) > 0 && Matches(exclude, f) { + continue + } + out = append(out, f) + } + return out +} diff --git a/internal/discover/discover_test.go b/internal/discover/discover_test.go new file mode 100644 index 0000000..bd36d24 --- /dev/null +++ b/internal/discover/discover_test.go @@ -0,0 +1,54 @@ +package discover + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMatches(t *testing.T) { + patterns := []string{"**/*.go", "docs/*.md"} + assert.True(t, Matches(patterns, "pkg/foo/bar.go")) + assert.True(t, Matches(patterns, "docs/readme.md")) + assert.False(t, Matches(patterns, "docs/sub/readme.md")) + assert.False(t, Matches(patterns, "README.txt")) +} + +func TestFilterByPaths(t *testing.T) { + files := []string{"a.go", "a_test.go", "docs/x.md", "README.txt"} + got := FilterByPaths(files, []string{"**/*.go"}, []string{"**/*_test.go"}) + assert.Equal(t, []string{"a.go"}, got) +} + +func TestFilterByPathsEmptyIncludeMeansAll(t *testing.T) { + files := []string{"a.go", "README.md"} + assert.Equal(t, files, FilterByPaths(files, nil, nil)) +} + +func TestModulesFindsNearestAncestor(t *testing.T) { + root := t.TempDir() + // Layout: + // /go.mod (root module) + // /services/auth/go.mod (nested module) + require.NoError(t, os.WriteFile(filepath.Join(root, "go.mod"), []byte("module r\n"), 0o600)) + require.NoError(t, os.MkdirAll(filepath.Join(root, "services", "auth"), 0o750)) + require.NoError(t, os.WriteFile(filepath.Join(root, "services", "auth", "go.mod"), []byte("module a\n"), 0o600)) + + files := []string{ + "services/auth/handler.go", + "services/auth/internal/db/db.go", + "pkg/other/file.go", + } + mods := Modules(root, files, []string{"go.mod"}) + assert.Equal(t, []string{".", "services/auth"}, mods) +} + +func TestModulesSkipsFilesWithNoMarker(t *testing.T) { + root := t.TempDir() + // No go.mod anywhere. + mods := Modules(root, []string{"pkg/foo/bar.go"}, []string{"go.mod"}) + assert.Empty(t, mods) +} diff --git a/internal/discover/modules.go b/internal/discover/modules.go new file mode 100644 index 0000000..89c86d7 --- /dev/null +++ b/internal/discover/modules.go @@ -0,0 +1,57 @@ +package discover + +import ( + "os" + "path/filepath" + "sort" +) + +// Modules returns the deduplicated, sorted list of module directories (relative +// to repoRoot) that own the given changed files. A module is defined as the +// nearest ancestor directory of a changed file that contains any of the +// markerFiles (e.g. go.mod). +// +// Files that have no ancestor marker are skipped. +func Modules(repoRoot string, changedFiles, markerFiles []string) []string { + if len(markerFiles) == 0 { + markerFiles = []string{"go.mod"} + } + seen := make(map[string]struct{}) + for _, f := range changedFiles { + dir := filepath.Dir(f) + mod := findModuleRoot(repoRoot, dir, markerFiles) + if mod == "" { + continue + } + seen[mod] = struct{}{} + } + out := make([]string, 0, len(seen)) + for m := range seen { + out = append(out, m) + } + sort.Strings(out) + return out +} + +// findModuleRoot walks up from startDir looking for any markerFile. Returns +// the relative path from repoRoot, or "." if the marker lives at the root. +// Returns the empty string if no marker is found up to the repo root. +func findModuleRoot(repoRoot, startDir string, markerFiles []string) string { + current := startDir + for { + for _, m := range markerFiles { + candidate := filepath.Join(repoRoot, current, m) + if _, err := os.Stat(candidate); err == nil { + return current + } + } + if current == "." || current == string(filepath.Separator) { + return "" + } + parent := filepath.Dir(current) + if parent == current { + return "" + } + current = parent + } +} diff --git a/internal/git/git.go b/internal/git/git.go new file mode 100644 index 0000000..d2c1e17 --- /dev/null +++ b/internal/git/git.go @@ -0,0 +1,195 @@ +// Package git is a thin wrapper around the `git` command-line tool. It exposes +// only the operations prenup needs and keeps the wrapper easy to fake in tests. +package git + +import ( + "bytes" + "errors" + "fmt" + "os/exec" + "strings" +) + +// Runner executes git commands against a specific working directory. A zero +// Runner uses the current working directory. +type Runner struct { + Dir string +} + +// New returns a Runner rooted at dir. If dir is empty, RepoRoot() callers will +// still resolve the repository relative to the current working directory. +func New(dir string) *Runner { + return &Runner{Dir: dir} +} + +// run executes git with the given args and returns stdout. stderr is captured +// and folded into the returned error on failure. +func (r *Runner) run(args ...string) (string, error) { + // args originate from internal callers building well-formed git command + // lines; the binary is hardcoded to "git". + cmd := exec.Command("git", args...) //nolint:gosec // G204: git binary is fixed; args are constructed internally. + if r.Dir != "" { + cmd.Dir = r.Dir + } + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + msg := strings.TrimSpace(stderr.String()) + if msg == "" { + msg = err.Error() + } + return "", fmt.Errorf("git %s: %s", strings.Join(args, " "), msg) + } + return stdout.String(), nil +} + +// RepoRoot returns the absolute path of the git repository containing dir (or +// the current working directory when dir is empty). +func RepoRoot(dir string) (string, error) { + r := &Runner{Dir: dir} + out, err := r.run("rev-parse", "--show-toplevel") + if err != nil { + return "", err + } + return strings.TrimSpace(out), nil +} + +// StagedFiles returns paths, relative to the repo root, that have staged +// changes vs HEAD. +func (r *Runner) StagedFiles() ([]string, error) { + out, err := r.run("diff", "--name-only", "--cached", "HEAD") + if err != nil { + // Empty repo (no HEAD) is not an error; treat as "everything staged is new". + if strings.Contains(err.Error(), "unknown revision") || strings.Contains(err.Error(), "bad revision") { + out, err = r.run("diff", "--name-only", "--cached") + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + return splitLines(out), nil +} + +// UnstagedFiles returns paths, relative to the repo root, with tracked but +// unstaged modifications. +func (r *Runner) UnstagedFiles() ([]string, error) { + out, err := r.run("diff", "--name-only") + if err != nil { + return nil, err + } + return splitLines(out), nil +} + +// UntrackedFiles returns paths, relative to the repo root, that are not +// tracked by git and not ignored by .gitignore. +func (r *Runner) UntrackedFiles() ([]string, error) { + out, err := r.run("ls-files", "--others", "--exclude-standard") + if err != nil { + return nil, err + } + return splitLines(out), nil +} + +// HasUnstagedChanges returns true when the worktree has unstaged modifications +// or untracked, non-ignored files. Used by stash-and-restore to decide whether +// any stashing is necessary. +func (r *Runner) HasUnstagedChanges() (bool, error) { + unstaged, err := r.UnstagedFiles() + if err != nil { + return false, err + } + if len(unstaged) > 0 { + return true, nil + } + untracked, err := r.UntrackedFiles() + if err != nil { + return false, err + } + return len(untracked) > 0, nil +} + +// TrackedFiles returns the set of files currently tracked by git, used as a +// baseline snapshot before running tasks that may generate new files. +func (r *Runner) TrackedFiles() (map[string]struct{}, error) { + out, err := r.run("ls-files") + if err != nil { + return nil, err + } + set := make(map[string]struct{}) + for _, line := range splitLines(out) { + set[line] = struct{}{} + } + return set, nil +} + +// PorcelainStatus returns the parsed output of +// `git status -s --porcelain=v1 --untracked-files=all` as a set of paths. Only +// the path portion is returned; status codes are discarded. +// +// `--untracked-files=all` is required: by default git collapses untracked +// directories to `dir/`, which would cause stage_output's per-file pattern +// matching to miss anything generated under a brand-new directory. +func (r *Runner) PorcelainStatus() ([]string, error) { + out, err := r.run("status", "-s", "--porcelain=v1", "--untracked-files=all") + if err != nil { + return nil, err + } + var files []string + for _, line := range splitLines(out) { + if len(line) < 4 { + continue + } + path := strings.TrimSpace(line[3:]) + // Rename and copy entries (status codes R / C) format the path + // as `old -> new`. Take only the destination path so callers + // can `git add` it without git treating the literal arrow as + // part of the filename. + if idx := strings.Index(path, " -> "); idx >= 0 { + path = path[idx+len(" -> "):] + } + files = append(files, path) + } + return files, nil +} + +// Add stages the named files. A nil or empty slice is a no-op. +func (r *Runner) Add(files []string) error { + if len(files) == 0 { + return nil + } + args := append([]string{"add", "--"}, files...) + if _, err := r.run(args...); err != nil { + return err + } + return nil +} + +// Version returns the `git --version` string, primarily for diagnostics. +func (r *Runner) Version() (string, error) { + out, err := r.run("--version") + if err != nil { + return "", err + } + return strings.TrimSpace(out), nil +} + +// splitLines splits s on newlines, trims carriage returns, and drops empties. +func splitLines(s string) []string { + raw := strings.Split(s, "\n") + out := make([]string, 0, len(raw)) + for _, line := range raw { + line = strings.TrimRight(line, "\r") + if line == "" { + continue + } + out = append(out, line) + } + return out +} + +// ErrNoRepo is returned when git cannot find a repository rooted at the given +// directory. +var ErrNoRepo = errors.New("not a git repository") diff --git a/internal/git/git_test.go b/internal/git/git_test.go new file mode 100644 index 0000000..ac8962e --- /dev/null +++ b/internal/git/git_test.go @@ -0,0 +1,181 @@ +package git + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/suite" +) + +// GitTestSuite groups behavioral tests for the git wrapper. Every method +// runs against a freshly-initialized repo with one committed file +// (README.md), so tests are independent and can run in any order. +type GitTestSuite struct { + suite.Suite + repo string // absolute path to the temp git repo + r *Runner // wrapper under test, scoped to repo +} + +func (s *GitTestSuite) SetupTest() { + dir := s.T().TempDir() + s.runGit(dir, "init", "-b", "main") + s.runGit(dir, "config", "user.email", "test@example.com") + s.runGit(dir, "config", "user.name", "test") + // Disable GPG signing so commit succeeds in environments where the + // developer's global git config requires a signing key the test + // process cannot access. + s.runGit(dir, "config", "commit.gpgsign", "false") + + s.Require().NoError(os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi\n"), 0o600)) + s.runGit(dir, "add", "README.md") + s.runGit(dir, "commit", "-m", "initial") + + s.repo = dir + s.r = New(dir) +} + +// runGit runs `git args...` in dir and fails the test on non-zero exit. +// Using s.Require() means a setup failure aborts the test immediately +// rather than letting later assertions run against a half-built repo. +func (s *GitTestSuite) runGit(dir string, args ...string) { + s.T().Helper() + cmd := exec.Command("git", args...) //nolint:gosec // G204: git binary is fixed in tests. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git %s: %s", strings.Join(args, " "), string(out)) +} + +// writeFile is a small helper used by tests that need to mutate the +// worktree before the assertion phase. +func (s *GitTestSuite) writeFile(name, content string) { + s.T().Helper() + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, name), []byte(content), 0o600)) +} + +func (s *GitTestSuite) TestRepoRoot() { + got, err := RepoRoot(s.repo) + s.Require().NoError(err) + + // macOS temp dirs live under /var/folders which symlinks to + // /private/var/folders; resolve both sides before comparing so a + // symlink-aware mismatch doesn't break this test on darwin. + wantEval, _ := filepath.EvalSymlinks(s.repo) + gotEval, _ := filepath.EvalSymlinks(got) + s.Equal(wantEval, gotEval) +} + +func (s *GitTestSuite) TestChangedFileCategories() { + s.writeFile("staged.txt", "s") + s.runGit(s.repo, "add", "staged.txt") + s.writeFile("README.md", "changed\n") // unstaged modification + s.writeFile("new.txt", "n") // untracked + + staged, err := s.r.StagedFiles() + s.Require().NoError(err) + s.Equal([]string{"staged.txt"}, staged) + + unstaged, err := s.r.UnstagedFiles() + s.Require().NoError(err) + s.Equal([]string{"README.md"}, unstaged) + + untracked, err := s.r.UntrackedFiles() + s.Require().NoError(err) + s.Equal([]string{"new.txt"}, untracked) + + has, err := s.r.HasUnstagedChanges() + s.Require().NoError(err) + s.True(has) +} + +// TestStashPushPopRestoresDirtyWorktree pins the round-trip behavior +// prenup relies on: Push() must hide unstaged + untracked changes +// (leaving staged content for the hook to evaluate), and Pop() must +// put both categories back exactly as they were. +func (s *GitTestSuite) TestStashPushPopRestoresDirtyWorktree() { + s.writeFile("staged.txt", "staged content\n") + s.runGit(s.repo, "add", "staged.txt") + s.writeFile("README.md", "unstaged change\n") + s.writeFile("new.txt", "untracked\n") + + stash, err := s.r.Push() + s.Require().NoError(err) + + unstaged, err := s.r.UnstagedFiles() + s.Require().NoError(err) + s.Empty(unstaged, "unstaged changes should be stashed away") + + untracked, err := s.r.UntrackedFiles() + s.Require().NoError(err) + s.Empty(untracked, "untracked files should be stashed away") + + staged, err := s.r.StagedFiles() + s.Require().NoError(err) + s.Contains(staged, "staged.txt") + + s.Require().NoError(stash.Pop()) + + unstaged, err = s.r.UnstagedFiles() + s.Require().NoError(err) + s.Contains(unstaged, "README.md") + + untracked, err = s.r.UntrackedFiles() + s.Require().NoError(err) + s.Contains(untracked, "new.txt") +} + +// TestStashPushNoopOnCleanWorktree pins that the wrapper handles the +// nothing-to-stash case gracefully -- prenup runs on every commit, so +// a panic or error here would block clean-worktree commits. +func (s *GitTestSuite) TestStashPushNoopOnCleanWorktree() { + stash, err := s.r.Push() + s.Require().NoError(err) + s.Require().NotNil(stash) + s.NoError(stash.Pop(), "Pop on a no-op stash must be safe") +} + +func (s *GitTestSuite) TestAddFiles() { + s.writeFile("a.txt", "a") + s.Require().NoError(s.r.Add([]string{"a.txt"})) + + staged, err := s.r.StagedFiles() + s.Require().NoError(err) + s.Contains(staged, "a.txt") +} + +// TestPorcelainStatusReturnsRenameDestination pins the parsing rule +// for `git status --porcelain=v1` rename entries, which look like +// `R old -> new`. PorcelainStatus must return only `new` so callers +// like stageGenerated can pass it to `git add` without git treating +// the literal arrow as part of the filename. +func (s *GitTestSuite) TestPorcelainStatusReturnsRenameDestination() { + // Seed an extra committed file so we have something to rename. + s.writeFile("original.txt", "hello\n") + s.runGit(s.repo, "add", "original.txt") + s.runGit(s.repo, "commit", "-m", "add original") + + // Stage a rename. `git mv` records both the deletion of the old + // path and the addition of the new one; with rename detection + // (which `--porcelain=v1` uses by default for staged changes) + // status reports a single R entry. + s.runGit(s.repo, "mv", "original.txt", "renamed.txt") + + files, err := s.r.PorcelainStatus() + s.Require().NoError(err) + s.Contains(files, "renamed.txt", "rename destination must be returned") + s.NotContains(files, "original.txt -> renamed.txt", "raw arrow form must not leak through") + s.NotContains(files, "original.txt", "source path of a rename must not be reported as a separate entry") +} + +func (s *GitTestSuite) TestTrackedFilesSnapshot() { + tracked, err := s.r.TrackedFiles() + s.Require().NoError(err) + _, has := tracked["README.md"] + s.True(has, "the seed README must show up in TrackedFiles") +} + +func TestGitSuite(t *testing.T) { + suite.Run(t, new(GitTestSuite)) +} diff --git a/internal/git/stash.go b/internal/git/stash.go new file mode 100644 index 0000000..fd55022 --- /dev/null +++ b/internal/git/stash.go @@ -0,0 +1,94 @@ +package git + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "strings" + "time" +) + +// StashMessagePrefix tags prenup-created stashes so we can identify and pop +// them even if the user creates other stashes in the meantime. +const StashMessagePrefix = "prenup:autostash" + +// Stash represents a prenup-managed stash that must be restored. +type Stash struct { + Ref string // e.g. "stash@{0}" at the time of creation + Message string + runner *Runner + active bool +} + +// Push stashes unstaged changes (keeping the index intact) so task execution +// runs against a worktree that matches the pending commit. Returns a Stash +// which the caller must Pop (typically via defer). +// +// If the worktree is clean, Push returns a zero-value Stash whose Pop is a +// no-op. Untracked files are included so they can't leak into task runs. +func (r *Runner) Push() (*Stash, error) { + dirty, err := r.HasUnstagedChanges() + if err != nil { + return nil, fmt.Errorf("detecting unstaged changes: %w", err) + } + if !dirty { + return &Stash{runner: r}, nil + } + + // Combine nanoseconds with a small random suffix so back-to-back stashes + // (e.g. tests, retries) cannot collide on the same message. + var nonce [4]byte + _, _ = rand.Read(nonce[:]) + msg := fmt.Sprintf("%s %d-%s", StashMessagePrefix, time.Now().UnixNano(), hex.EncodeToString(nonce[:])) + _, err = r.run("stash", "push", "--include-untracked", "--keep-index", "--message", msg) + if err != nil { + return nil, fmt.Errorf("creating stash: %w", err) + } + + ref, err := r.findStashRef(msg) + if err != nil { + return nil, err + } + return &Stash{Ref: ref, Message: msg, runner: r, active: true}, nil +} + +// Pop restores a stash previously created via Push. Safe to call on a zero +// Stash or one that has already been popped. +func (s *Stash) Pop() error { + if s == nil || !s.active { + return nil + } + // Use the recorded message to re-locate the stash, since running tasks + // may have added other stashes in between. + ref, err := s.runner.findStashRef(s.Message) + if err != nil { + return err + } + if ref == "" { + return fmt.Errorf("prenup stash %q not found when restoring", s.Message) + } + if _, err := s.runner.run("stash", "pop", ref); err != nil { + return fmt.Errorf("restoring stash: %w", err) + } + s.active = false + return nil +} + +// findStashRef walks `git stash list` and returns the ref matching msg, or +// the empty string if not found. +func (r *Runner) findStashRef(msg string) (string, error) { + out, err := r.run("stash", "list") + if err != nil { + return "", fmt.Errorf("listing stashes: %w", err) + } + for _, line := range splitLines(out) { + // Format: "stash@{N}: On branch: message" + if !strings.Contains(line, msg) { + continue + } + if idx := strings.Index(line, ":"); idx > 0 { + return line[:idx], nil + } + } + return "", nil +} diff --git a/internal/hook/install.go b/internal/hook/install.go new file mode 100644 index 0000000..0479995 --- /dev/null +++ b/internal/hook/install.go @@ -0,0 +1,142 @@ +// Package hook installs and uninstalls the .git/hooks/pre-commit entry. +package hook + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// Mode chooses how install handles an existing hook. +type Mode int + +const ( + // ModeAbort refuses to overwrite; returns ExistsError. + ModeAbort Mode = iota + // ModeReplace backs up the existing hook and replaces it with prenup. + ModeReplace + // ModeChain saves the existing hook as pre-commit.local and invokes it + // from the prenup-managed wrapper before running prenup. + ModeChain + // ModeForce overwrites without backup. + ModeForce +) + +// ExistsError is returned by Install when a non-prenup hook already exists and +// Mode is ModeAbort. +type ExistsError struct{ Path string } + +func (e *ExistsError) Error() string { + return fmt.Sprintf("hook already exists at %s (use --force, --replace, or --chain)", e.Path) +} + +// PrenupMarker is written into the managed hook so uninstall can verify +// authorship before removing the file. +const PrenupMarker = "# prenup-managed-hook" + +// Install writes a pre-commit hook at repoRoot/.git/hooks/pre-commit that +// invokes the prenup binary found at binaryPath. mode controls collision +// handling. +func Install(repoRoot, binaryPath string, mode Mode) error { + hookPath := filepath.Join(repoRoot, ".git", "hooks", "pre-commit") + + // Git hooks live in .git/hooks (already restrictive, owned by the user). + // 0o750 satisfies gosec G301 while remaining traversable for the user. + if err := os.MkdirAll(filepath.Dir(hookPath), 0o750); err != nil { + return fmt.Errorf("ensuring hooks dir: %w", err) + } + + // hookPath is constructed from the validated repoRoot plus a fixed suffix. + existing, err := os.ReadFile(hookPath) //nolint:gosec // G304: hookPath is a constructed path under .git/hooks. + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("reading existing hook: %w", err) + } + + existedAndManaged := len(existing) > 0 && strings.Contains(string(existing), PrenupMarker) + + switch { + case len(existing) == 0 || existedAndManaged: + // Clear to write. + case mode == ModeAbort: + return &ExistsError{Path: hookPath} + case mode == ModeReplace: + backup := hookPath + ".prenup-backup" + if err := os.Rename(hookPath, backup); err != nil { + return fmt.Errorf("backing up existing hook: %w", err) + } + case mode == ModeChain: + localPath := filepath.Join(repoRoot, ".git", "hooks", "pre-commit.local") + if err := os.Rename(hookPath, localPath); err != nil { + return fmt.Errorf("saving existing hook as pre-commit.local: %w", err) + } + // Hooks must be executable; chmod ensures the owner+group can run it. + if err := os.Chmod(localPath, 0o750); err != nil { //nolint:gosec // G302: git hooks must be executable. + return fmt.Errorf("chmod pre-commit.local: %w", err) + } + case mode == ModeForce: + // Overwrite without backup. + } + + script := generateScript(binaryPath, mode == ModeChain) + // Git requires the hook to be executable; the owner+group can run it. + if err := os.WriteFile(hookPath, []byte(script), 0o750); err != nil { //nolint:gosec // G306: git hooks must be executable. + return fmt.Errorf("writing hook: %w", err) + } + return nil +} + +// generateScript returns the shell script body for the managed hook. +// +// `set -e` is intentional: if the chained pre-commit.local exits non-zero we +// want to fail-closed and block the commit before prenup even starts. The +// recorded prenup binary path is invoked with "$@" so any future Git hooks +// that do receive arguments forward them; today Git invokes pre-commit with +// none, so this is forward-compatible polish only. +func generateScript(binaryPath string, chain bool) string { + var b strings.Builder + b.WriteString("#!/usr/bin/env bash\n") + b.WriteString(PrenupMarker + "\n") + b.WriteString("# Do not edit this file by hand; regenerate via `prenup install`.\n") + b.WriteString("set -e\n") + if chain { + b.WriteString(`LOCAL="$(dirname "$0")/pre-commit.local"` + "\n") + // fail-closed: a failing local hook aborts the script before exec. + b.WriteString(`if [ -x "$LOCAL" ]; then "$LOCAL" "$@"; fi` + "\n") + } + fmt.Fprintf(&b, "exec %q \"$@\"\n", binaryPath) + return b.String() +} + +// Uninstall removes the managed hook at repoRoot. If a .prenup-backup exists, +// it is restored; otherwise the hook is simply deleted. Returns nil if no +// managed hook is installed. +func Uninstall(repoRoot string) error { + hookPath := filepath.Join(repoRoot, ".git", "hooks", "pre-commit") + data, err := os.ReadFile(hookPath) //nolint:gosec // G304: hookPath is constructed under .git/hooks. + if os.IsNotExist(err) { + return nil + } + if err != nil { + return fmt.Errorf("reading hook: %w", err) + } + if !strings.Contains(string(data), PrenupMarker) { + return fmt.Errorf("hook at %s was not installed by prenup; leaving it alone", hookPath) + } + + backup := hookPath + ".prenup-backup" + if _, err := os.Stat(backup); err == nil { + if err := os.Rename(backup, hookPath); err != nil { + return fmt.Errorf("restoring backup: %w", err) + } + return nil + } + localPath := filepath.Join(repoRoot, ".git", "hooks", "pre-commit.local") + if _, err := os.Stat(localPath); err == nil { + if err := os.Rename(localPath, hookPath); err != nil { + return fmt.Errorf("restoring chained pre-commit.local: %w", err) + } + return nil + } + return os.Remove(hookPath) +} diff --git a/internal/hook/install_test.go b/internal/hook/install_test.go new file mode 100644 index 0000000..75bfc4b --- /dev/null +++ b/internal/hook/install_test.go @@ -0,0 +1,130 @@ +package hook + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/suite" +) + +// HookInstallTestSuite groups behavioral tests for Install/Uninstall. +// Each method gets a fresh fake repo with .git/hooks/ pre-created so +// the code under test has somewhere to write; tests do not need a real +// git repo for these checks. +type HookInstallTestSuite struct { + suite.Suite + repo string // absolute path to the temp repo dir + hook string // absolute path to .git/hooks/pre-commit inside repo +} + +func (s *HookInstallTestSuite) SetupTest() { + dir := s.T().TempDir() + s.Require().NoError(os.MkdirAll(filepath.Join(dir, ".git", "hooks"), 0o750)) + s.repo = dir + s.hook = filepath.Join(dir, ".git", "hooks", "pre-commit") +} + +// writeHook drops a fake (non-prenup) executable hook script at the +// pre-commit path. Used by tests that exercise the +// existing-hook-handling modes. +func (s *HookInstallTestSuite) writeHook(content string) { + s.T().Helper() + //nolint:gosec // G306: simulating executable git hook in tests. + s.Require().NoError(os.WriteFile(s.hook, []byte(content), 0o750)) +} + +// readHook reads the managed pre-commit file. Wrapped so the //nolint +// gosec annotation does not have to repeat at every call site. +func (s *HookInstallTestSuite) readHook(path string) string { + s.T().Helper() + //nolint:gosec // G304: path is constructed under the test TempDir. + data, err := os.ReadFile(path) + s.Require().NoError(err) + return string(data) +} + +func (s *HookInstallTestSuite) TestInstallOnEmptyWritesManagedHook() { + s.Require().NoError(Install(s.repo, "/usr/local/bin/prenup", ModeAbort)) + + body := s.readHook(s.hook) + s.Contains(body, PrenupMarker) + s.Contains(body, `"/usr/local/bin/prenup"`) + + info, err := os.Stat(s.hook) + s.Require().NoError(err) + s.NotZero(info.Mode()&0o100, "hook must be executable") +} + +func (s *HookInstallTestSuite) TestInstallAbortsOnExistingNonPrenupHook() { + s.writeHook("#!/usr/bin/env bash\necho hi\n") + + err := Install(s.repo, "/usr/local/bin/prenup", ModeAbort) + var existsErr *ExistsError + s.Require().ErrorAs(err, &existsErr, "expected ExistsError, got %v", err) +} + +func (s *HookInstallTestSuite) TestInstallReplaceBacksUp() { + original := "#!/usr/bin/env bash\necho original\n" + s.writeHook(original) + + s.Require().NoError(Install(s.repo, "/usr/local/bin/prenup", ModeReplace)) + + s.Equal(original, s.readHook(s.hook+".prenup-backup")) + s.Contains(s.readHook(s.hook), PrenupMarker) +} + +func (s *HookInstallTestSuite) TestInstallChainKeepsOriginalAsLocal() { + original := "#!/usr/bin/env bash\necho original\n" + s.writeHook(original) + + s.Require().NoError(Install(s.repo, "/usr/local/bin/prenup", ModeChain)) + + s.Equal(original, s.readHook(filepath.Join(s.repo, ".git", "hooks", "pre-commit.local"))) + + managed := s.readHook(s.hook) + s.Contains(managed, PrenupMarker) + // In chain mode the managed hook must reference the saved-aside + // local hook so it can delegate to the user's original script. + s.Contains(managed, "pre-commit.local") +} + +// TestInstallReplacesManagedHookEvenInAbortMode pins that ModeAbort's +// "refuse to clobber" behavior does NOT apply when the existing hook +// is itself a prenup-managed hook -- otherwise upgrades and binary +// path changes would be impossible without a separate uninstall step. +func (s *HookInstallTestSuite) TestInstallReplacesManagedHookEvenInAbortMode() { + s.Require().NoError(Install(s.repo, "/old/prenup", ModeAbort)) + s.Require().NoError(Install(s.repo, "/new/prenup", ModeAbort)) + + body := s.readHook(s.hook) + s.Contains(body, "/new/prenup") + s.NotContains(body, "/old/prenup") +} + +func (s *HookInstallTestSuite) TestUninstallRestoresBackup() { + original := "#!/usr/bin/env bash\necho original\n" + s.writeHook(original) + s.Require().NoError(Install(s.repo, "/usr/local/bin/prenup", ModeReplace)) + + s.Require().NoError(Uninstall(s.repo)) + s.Equal(original, s.readHook(s.hook)) + _, err := os.Stat(s.hook + ".prenup-backup") + s.True(os.IsNotExist(err), "backup must be removed after restore") +} + +func (s *HookInstallTestSuite) TestUninstallRefusesUnmanagedHook() { + s.writeHook("#!/usr/bin/env bash\necho hi\n") + + err := Uninstall(s.repo) + s.Require().Error(err) + s.Contains(err.Error(), "not installed by prenup") +} + +func (s *HookInstallTestSuite) TestUninstallNoOpWhenMissing() { + s.Require().NoError(Uninstall(s.repo)) +} + +func TestHookInstallSuite(t *testing.T) { + suite.Run(t, new(HookInstallTestSuite)) +} diff --git a/internal/lock/flock_unix.go b/internal/lock/flock_unix.go new file mode 100644 index 0000000..147f12d --- /dev/null +++ b/internal/lock/flock_unix.go @@ -0,0 +1,19 @@ +//go:build unix + +package lock + +import ( + "os" + "syscall" +) + +// flockNB attempts a non-blocking exclusive advisory lock. Returns +// syscall.EWOULDBLOCK if another process already holds the lock. +func flockNB(f *os.File) error { + return syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) +} + +// funlock releases a previously-acquired advisory lock. +func funlock(f *os.File) error { + return syscall.Flock(int(f.Fd()), syscall.LOCK_UN) +} diff --git a/internal/lock/lock.go b/internal/lock/lock.go new file mode 100644 index 0000000..6210e51 --- /dev/null +++ b/internal/lock/lock.go @@ -0,0 +1,150 @@ +// Package lock provides a per-repository advisory lock so two concurrent +// `prenup run` invocations cannot race on the worktree. +// +// Some Git GUIs and IDE integrations invoke `git commit` more than once in +// quick succession; without coordination, two prenup runs can both stash, +// run tasks, and pop in interleaved order, corrupting `stage_output` and +// the user's stash list. The lock guarantees serialization at the repo +// scope. +// +// The implementation uses an OS-level advisory lock (flock(2) on Unix) on a +// file under .git/. Advisory locks are automatically released when the +// holding process dies, so there is no stale-PID file to clean up after a +// crash or kill -9. +package lock + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" +) + +// LockFileName is the basename of the lock file inside .git/. +const LockFileName = "prenup.lock" + +// ErrContended is returned by Acquire when another prenup run is already +// holding the lock. Callers should surface a friendly message and exit +// with a non-zero status rather than block. +var ErrContended = errors.New("another prenup run is already in progress for this repository") + +// Lock is a held advisory lock on a repository. Release with Close. +type Lock struct { + path string + f *os.File +} + +// Acquire takes a non-blocking exclusive advisory lock on +// repoRoot/.git/prenup.lock. It returns ErrContended when another process +// already holds the lock, and any other error if the file system itself is +// the problem. +// +// The returned Lock owns the file descriptor; the caller must call Close +// (typically via defer) to release the lock and remove the file. +func Acquire(repoRoot string) (*Lock, error) { + gitDir := filepath.Join(repoRoot, ".git") + // Repos with separate gitdirs (worktrees, submodules) write a `.git` + // regular file pointing at the real dir; resolve it so the lock lives + // alongside the repo's actual git metadata. If `.git` doesn't exist + // yet, MkdirAll below will create it -- callers are responsible for + // having pointed us at a real repo root. + if info, err := os.Stat(gitDir); err == nil && !info.IsDir() { + if resolved, ok := resolveGitFile(gitDir); ok { + gitDir = resolved + } + } + if err := os.MkdirAll(gitDir, 0o750); err != nil { + return nil, fmt.Errorf("ensuring lock directory: %w", err) + } + path := filepath.Join(gitDir, LockFileName) + // O_CREATE so first-run users don't need a pre-existing lock file. + // 0600: only the user running prenup needs to read/write it. + // G304 suppressed: path = repoRoot/.git/prenup.lock; basename is fixed. + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600) //nolint:gosec + if err != nil { + return nil, fmt.Errorf("opening lock file %s: %w", path, err) + } + if err := flockNB(f); err != nil { + _ = f.Close() + if errors.Is(err, syscall.EWOULDBLOCK) { + return nil, ErrContended + } + return nil, fmt.Errorf("locking %s: %w", path, err) + } + return &Lock{path: path, f: f}, nil +} + +// Close releases the advisory lock and closes the underlying file. It is +// safe to call Close more than once; the second call is a no-op. Close does +// not delete the lock file -- the inode is harmless when unlocked, and +// keeping it avoids a TOCTOU race where another process opens the file +// between our unlink and unlock. +func (l *Lock) Close() error { + if l == nil || l.f == nil { + return nil + } + // Best-effort unlock; the kernel also releases when the fd closes. + _ = funlock(l.f) + err := l.f.Close() + l.f = nil + return err +} + +// Path returns the absolute path of the lock file. Useful for diagnostics. +func (l *Lock) Path() string { + if l == nil { + return "" + } + return l.path +} + +// resolveGitFile reads a `.git` regular file (used by worktrees and +// submodules) and returns the directory it points at. +func resolveGitFile(gitFile string) (string, bool) { + data, err := os.ReadFile(gitFile) //nolint:gosec // G304: callers pass a fixed .git path under repoRoot. + if err != nil { + return "", false + } + const prefix = "gitdir:" + for _, line := range splitLines(string(data)) { + if len(line) > len(prefix) && line[:len(prefix)] == prefix { + return trimSpace(line[len(prefix):]), true + } + } + return "", false +} + +func splitLines(s string) []string { + var out []string + start := 0 + for i := range len(s) { + if s[i] == '\n' { + out = append(out, s[start:i]) + start = i + 1 + } + } + if start < len(s) { + out = append(out, s[start:]) + } + return out +} + +func trimSpace(s string) string { + start, end := 0, len(s) + for start < end && isSpace(s[start]) { + start++ + } + for end > start && isSpace(s[end-1]) { + end-- + } + return s[start:end] +} + +func isSpace(b byte) bool { + switch b { + case ' ', '\t', '\r', '\n': + return true + } + return false +} diff --git a/internal/lock/lock_test.go b/internal/lock/lock_test.go new file mode 100644 index 0000000..851482d --- /dev/null +++ b/internal/lock/lock_test.go @@ -0,0 +1,113 @@ +package lock + +import ( + "errors" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +// fakeRepo creates a directory containing an empty .git/ subdir, mimicking +// the layout Acquire expects. Returns the repo root. +func fakeRepo(t *testing.T) string { + t.Helper() + repo := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(repo, ".git"), 0o750)) + return repo +} + +func TestAcquireReleaseRoundTrip(t *testing.T) { + t.Parallel() + repo := fakeRepo(t) + + l, err := Acquire(repo) + require.NoError(t, err) + require.NotNil(t, l) + require.FileExists(t, l.Path()) + require.Equal(t, filepath.Join(repo, ".git", LockFileName), l.Path()) + + require.NoError(t, l.Close()) + // Second Close is a no-op, must not panic or error. + require.NoError(t, l.Close()) +} + +func TestAcquireContendedReturnsErrContended(t *testing.T) { + t.Parallel() + repo := fakeRepo(t) + + first, err := Acquire(repo) + require.NoError(t, err) + defer func() { _ = first.Close() }() + + second, err := Acquire(repo) + require.Nil(t, second) + require.ErrorIs(t, err, ErrContended) +} + +func TestAcquireAfterReleaseSucceeds(t *testing.T) { + t.Parallel() + repo := fakeRepo(t) + + first, err := Acquire(repo) + require.NoError(t, err) + require.NoError(t, first.Close()) + + second, err := Acquire(repo) + require.NoError(t, err) + require.NotNil(t, second) + require.NoError(t, second.Close()) +} + +func TestAcquireCreatesGitDirIfMissing(t *testing.T) { + t.Parallel() + repo := t.TempDir() // no .git/ at all + + l, err := Acquire(repo) + require.NoError(t, err) + defer func() { _ = l.Close() }() + + info, statErr := os.Stat(filepath.Join(repo, ".git")) + require.NoError(t, statErr) + require.True(t, info.IsDir()) +} + +func TestAcquireConcurrent(t *testing.T) { + t.Parallel() + repo := fakeRepo(t) + + const goroutines = 16 + var ( + wg sync.WaitGroup + mu sync.Mutex + successes int + contended int + other []error + ) + wg.Add(goroutines) + for range goroutines { + go func() { + defer wg.Done() + l, err := Acquire(repo) + mu.Lock() + defer mu.Unlock() + switch { + case err == nil: + successes++ + // Hold briefly so others observe contention. + _ = l.Close() + case errors.Is(err, ErrContended): + contended++ + default: + other = append(other, err) + } + }() + } + wg.Wait() + + require.Empty(t, other, "no unexpected errors") + require.GreaterOrEqual(t, successes, 1, "at least one acquirer must win") + require.Equal(t, goroutines, successes+contended, "every goroutine accounted for") +} diff --git a/internal/runner/events.go b/internal/runner/events.go new file mode 100644 index 0000000..de6b137 --- /dev/null +++ b/internal/runner/events.go @@ -0,0 +1,114 @@ +// Package runner executes selected tasks against the changed modules and emits +// a stream of events that the UI layers render (human TUI, markdown digest, +// NDJSON for agents). +package runner + +import "time" + +// EventKind names the shape of an Event. +type EventKind string + +// Event kinds emitted by the runner. Consumed by all UI sinks (human, markdown, +// json) and exposed verbatim on the JSON wire format. +const ( + EventRunStarted EventKind = "run_started" + EventTaskStarted EventKind = "task_started" + EventLine EventKind = "line" + EventTaskCompleted EventKind = "task_completed" + EventRunCompleted EventKind = "run_completed" + EventNotice EventKind = "notice" +) + +// Stream identifies which pipe an output line came from. +type Stream string + +// Stream values for EventLine events. +const ( + StreamStdout Stream = "stdout" + StreamStderr Stream = "stderr" +) + +// TaskStatus is the terminal status of a task run. +type TaskStatus string + +// TaskStatus values reported on EventTaskCompleted. +const ( + TaskStatusDone TaskStatus = "done" + TaskStatusFailed TaskStatus = "failed" + TaskStatusSkipped TaskStatus = "skipped" +) + +// Event is the unit the runner emits to subscribed UIs. +type Event struct { + Kind EventKind `json:"type"` + Time time.Time `json:"time,omitempty"` + + // Identifying fields (populated as relevant to Kind). + Version string `json:"version,omitempty"` + Task string `json:"task,omitempty"` + Module string `json:"module,omitempty"` + + // Task-start fields. Carry the resolved (post-template-expansion) + // command and working directory so a consumer reading a failure + // transcript has everything it needs to reproduce the run without + // also reading .prenup.yaml. + Command string `json:"command,omitempty"` + WorkingDir string `json:"working_dir,omitempty"` + + // Line-specific fields. + Stream Stream `json:"stream,omitempty"` + Text string `json:"text,omitempty"` + + // Task-completion fields. + Status TaskStatus `json:"status,omitempty"` + DurationMs int64 `json:"duration_ms,omitempty"` + Error string `json:"error,omitempty"` + + // Run-level fields. + Modules []string `json:"modules,omitempty"` + Tasks []string `json:"tasks,omitempty"` + Succeeded int `json:"succeeded,omitempty"` + Failed int `json:"failed,omitempty"` + ExitCode int `json:"exit_code,omitempty"` + + // RepoRoot is the absolute path of the git repository prenup was + // invoked from. Set on EventRunStarted so a JSON consumer can anchor + // every subsequent task's working_dir without inferring it from + // substring matches. + RepoRoot string `json:"repo_root,omitempty"` + + // FailedTasks lists the names of tasks that ended in TaskStatusFailed. + // Set on EventRunCompleted so a consumer with a long log can jump + // directly to the failures without rescanning every task_completed + // event. + FailedTasks []string `json:"failed_tasks,omitempty"` + + // Free-form human-readable message used by notices. + Message string `json:"message,omitempty"` +} + +// Sink consumes events. +// +// Implementations MUST be safe for concurrent Emit calls from multiple +// goroutines: parallel per-module task runs (see runner.runParallel) fan out +// goroutines that each call Emit on the same sink. Sinks that wrap shared +// state (writers, channels, maps) need their own synchronization. +// +// Close is called once, by the orchestrator that owns the sink, after the +// final Run-level event has been delivered. Implementations may use it to +// flush buffered output. Close need not be safe to call concurrently with +// Emit. +type Sink interface { + Emit(Event) + Close() error +} + +// DiscardSink drops every event. Useful as a default for callers that do not +// supply a Sink, and for tests. +type DiscardSink struct{} + +// Emit on a DiscardSink does nothing. +func (DiscardSink) Emit(Event) {} + +// Close on a DiscardSink does nothing. +func (DiscardSink) Close() error { return nil } diff --git a/internal/runner/exec.go b/internal/runner/exec.go new file mode 100644 index 0000000..9485dff --- /dev/null +++ b/internal/runner/exec.go @@ -0,0 +1,115 @@ +package runner + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "sync" + "syscall" + "time" +) + +// Executor runs a shell command and streams stdout+stderr lines to a callback. +// It is an interface so tests can swap in a fake. +type Executor interface { + Run(ctx context.Context, command, workingDir string, env map[string]string, onLine func(Stream, string)) error +} + +// BashExecutor invokes `bash -c command`. +// +// On context cancellation it sends SIGTERM and gives the child up to +// gracefulShutdown to clean up before SIGKILL. Long-running tools (test +// runners, linters) often want to flush output or remove temp files; the +// default exec.CommandContext behavior of immediate SIGKILL would skip that. +type BashExecutor struct { + // GracefulShutdown caps how long to wait after cancel before the + // process is forcefully killed. Zero means use the default of 5s. + GracefulShutdown time.Duration +} + +// gracefulShutdownDefault is the wait between SIGTERM and SIGKILL when +// GracefulShutdown is unset. +const gracefulShutdownDefault = 5 * time.Second + +// Run executes command via bash -c. stdout and stderr are read concurrently +// and forwarded to onLine preserving the originating stream. +func (b BashExecutor) Run(ctx context.Context, command, workingDir string, env map[string]string, onLine func(Stream, string)) error { + cmd := exec.CommandContext(ctx, "bash", "-c", command) //nolint:gosec // command comes from user config + if workingDir != "" { + cmd.Dir = workingDir + } + if len(env) > 0 { + cmd.Env = append(os.Environ(), flattenEnv(env)...) + } + // On ctx cancel, ask politely first; SIGKILL after the grace period. + cmd.Cancel = func() error { + // SIGTERM may legitimately fail (process already gone); ignore. + _ = cmd.Process.Signal(syscall.SIGTERM) + return nil + } + grace := b.GracefulShutdown + if grace <= 0 { + grace = gracefulShutdownDefault + } + cmd.WaitDelay = grace + + stdout, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("stdout pipe: %w", err) + } + stderr, err := cmd.StderrPipe() + if err != nil { + return fmt.Errorf("stderr pipe: %w", err) + } + + if err := cmd.Start(); err != nil { + return fmt.Errorf("starting command: %w", err) + } + + var wg sync.WaitGroup + wg.Add(2) + go pumpLines(&wg, stdout, StreamStdout, onLine) + go pumpLines(&wg, stderr, StreamStderr, onLine) + wg.Wait() + + if err := cmd.Wait(); err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + return fmt.Errorf("exit status %d", exitErr.ExitCode()) + } + return err + } + return nil +} + +// pumpLines reads from r line-by-line up to maxScannerLine. If a single line +// exceeds the buffer, the scanner stops; we surface that to the caller as a +// synthetic stderr-style notice on the same stream so users know their +// output was truncated. +func pumpLines(wg *sync.WaitGroup, r io.Reader, stream Stream, onLine func(Stream, string)) { + defer wg.Done() + scanner := bufio.NewScanner(r) + scanner.Buffer(make([]byte, 64*1024), maxScannerLine) + for scanner.Scan() { + onLine(stream, scanner.Text()) + } + if err := scanner.Err(); err != nil { + onLine(stream, fmt.Sprintf("[prenup] output truncated: %v (line exceeded %d bytes)", err, maxScannerLine)) + } +} + +// maxScannerLine bounds a single line's length. 1 MB is generous for human +// output but small enough to keep memory bounded against pathological tools. +const maxScannerLine = 1024 * 1024 + +func flattenEnv(env map[string]string) []string { + out := make([]string, 0, len(env)) + for k, v := range env { + out = append(out, k+"="+v) + } + return out +} diff --git a/internal/runner/mocks/mocks.go b/internal/runner/mocks/mocks.go new file mode 100644 index 0000000..c78306e --- /dev/null +++ b/internal/runner/mocks/mocks.go @@ -0,0 +1,225 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mocks + +import ( + "context" + + "github.com/c2fo/prenup/internal/runner" + mock "github.com/stretchr/testify/mock" +) + +// NewSink creates a new instance of Sink. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSink(t interface { + mock.TestingT + Cleanup(func()) +}) *Sink { + mock := &Sink{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// Sink is an autogenerated mock type for the Sink type +type Sink struct { + mock.Mock +} + +type Sink_Expecter struct { + mock *mock.Mock +} + +func (_m *Sink) EXPECT() *Sink_Expecter { + return &Sink_Expecter{mock: &_m.Mock} +} + +// Close provides a mock function for the type Sink +func (_mock *Sink) Close() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Sink_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type Sink_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *Sink_Expecter) Close() *Sink_Close_Call { + return &Sink_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *Sink_Close_Call) Run(run func()) *Sink_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Sink_Close_Call) Return(err error) *Sink_Close_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Sink_Close_Call) RunAndReturn(run func() error) *Sink_Close_Call { + _c.Call.Return(run) + return _c +} + +// Emit provides a mock function for the type Sink +func (_mock *Sink) Emit(event runner.Event) { + _mock.Called(event) + return +} + +// Sink_Emit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Emit' +type Sink_Emit_Call struct { + *mock.Call +} + +// Emit is a helper method to define mock.On call +// - event runner.Event +func (_e *Sink_Expecter) Emit(event interface{}) *Sink_Emit_Call { + return &Sink_Emit_Call{Call: _e.mock.On("Emit", event)} +} + +func (_c *Sink_Emit_Call) Run(run func(event runner.Event)) *Sink_Emit_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 runner.Event + if args[0] != nil { + arg0 = args[0].(runner.Event) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *Sink_Emit_Call) Return() *Sink_Emit_Call { + _c.Call.Return() + return _c +} + +func (_c *Sink_Emit_Call) RunAndReturn(run func(event runner.Event)) *Sink_Emit_Call { + _c.Run(run) + return _c +} + +// NewExecutor creates a new instance of Executor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewExecutor(t interface { + mock.TestingT + Cleanup(func()) +}) *Executor { + mock := &Executor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// Executor is an autogenerated mock type for the Executor type +type Executor struct { + mock.Mock +} + +type Executor_Expecter struct { + mock *mock.Mock +} + +func (_m *Executor) EXPECT() *Executor_Expecter { + return &Executor_Expecter{mock: &_m.Mock} +} + +// Run provides a mock function for the type Executor +func (_mock *Executor) Run(ctx context.Context, command string, workingDir string, env map[string]string, onLine func(runner.Stream, string)) error { + ret := _mock.Called(ctx, command, workingDir, env, onLine) + + if len(ret) == 0 { + panic("no return value specified for Run") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, map[string]string, func(runner.Stream, string)) error); ok { + r0 = returnFunc(ctx, command, workingDir, env, onLine) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Executor_Run_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Run' +type Executor_Run_Call struct { + *mock.Call +} + +// Run is a helper method to define mock.On call +// - ctx context.Context +// - command string +// - workingDir string +// - env map[string]string +// - onLine func(runner.Stream, string) +func (_e *Executor_Expecter) Run(ctx interface{}, command interface{}, workingDir interface{}, env interface{}, onLine interface{}) *Executor_Run_Call { + return &Executor_Run_Call{Call: _e.mock.On("Run", ctx, command, workingDir, env, onLine)} +} + +func (_c *Executor_Run_Call) Run(run func(ctx context.Context, command string, workingDir string, env map[string]string, onLine func(runner.Stream, string))) *Executor_Run_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 map[string]string + if args[3] != nil { + arg3 = args[3].(map[string]string) + } + var arg4 func(runner.Stream, string) + if args[4] != nil { + arg4 = args[4].(func(runner.Stream, string)) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) + }) + return _c +} + +func (_c *Executor_Run_Call) Return(err error) *Executor_Run_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Executor_Run_Call) RunAndReturn(run func(ctx context.Context, command string, workingDir string, env map[string]string, onLine func(runner.Stream, string)) error) *Executor_Run_Call { + _c.Call.Return(run) + return _c +} diff --git a/internal/runner/multisink.go b/internal/runner/multisink.go new file mode 100644 index 0000000..289b69d --- /dev/null +++ b/internal/runner/multisink.go @@ -0,0 +1,31 @@ +package runner + +// MultiSink fans every event out to multiple downstream sinks. Useful for +// combining the live TUI with a post-run summary printer. +type MultiSink struct { + sinks []Sink +} + +// NewMultiSink returns a Sink that delegates to each of the given sinks. +func NewMultiSink(sinks ...Sink) *MultiSink { + return &MultiSink{sinks: sinks} +} + +// Emit forwards ev to every sink. +func (m *MultiSink) Emit(ev Event) { + for _, s := range m.sinks { + s.Emit(ev) + } +} + +// Close closes every sink; the first error is returned and the rest are +// attempted regardless. +func (m *MultiSink) Close() error { + var firstErr error + for _, s := range m.sinks { + if err := s.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} diff --git a/internal/runner/multisink_test.go b/internal/runner/multisink_test.go new file mode 100644 index 0000000..20b95a1 --- /dev/null +++ b/internal/runner/multisink_test.go @@ -0,0 +1,54 @@ +package runner + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type recordingSink struct { + events []Event + closeErr error + closed bool +} + +func (r *recordingSink) Emit(ev Event) { r.events = append(r.events, ev) } +func (r *recordingSink) Close() error { r.closed = true; return r.closeErr } + +// TestMultiSinkEmitsToAll verifies the basic fan-out contract. +func TestMultiSinkEmitsToAll(t *testing.T) { + t.Parallel() + a, b := &recordingSink{}, &recordingSink{} + m := NewMultiSink(a, b) + m.Emit(Event{Kind: EventNotice, Message: "hi"}) + + assert.Len(t, a.events, 1) + assert.Len(t, b.events, 1) +} + +// TestMultiSinkCloseAggregatesErrors confirms that when one sink's Close +// fails, the other sinks are still closed (so resources don't leak) and the +// first error is what gets returned to the caller. +func TestMultiSinkCloseAggregatesErrors(t *testing.T) { + t.Parallel() + first := &recordingSink{closeErr: errors.New("first failed")} + second := &recordingSink{} + third := &recordingSink{closeErr: errors.New("third failed")} + + m := NewMultiSink(first, second, third) + err := m.Close() + + require.EqualError(t, err, "first failed", "first error must propagate") + assert.True(t, first.closed) + assert.True(t, second.closed, "second sink must be closed even if first errored") + assert.True(t, third.closed, "third sink must be closed regardless of earlier errors") +} + +// TestMultiSinkCloseAllSuccess returns nil when every sink closes cleanly. +func TestMultiSinkCloseAllSuccess(t *testing.T) { + t.Parallel() + m := NewMultiSink(&recordingSink{}, &recordingSink{}) + assert.NoError(t, m.Close()) +} diff --git a/internal/runner/runner.go b/internal/runner/runner.go new file mode 100644 index 0000000..027421d --- /dev/null +++ b/internal/runner/runner.go @@ -0,0 +1,502 @@ +package runner + +import ( + "context" + "fmt" + "runtime" + "sort" + "strings" + "sync" + "time" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/discover" + "github.com/c2fo/prenup/internal/git" +) + +// Plan describes what a run will do. It is computed up-front so it can be +// shown by `prenup plan` without executing anything. +type Plan struct { + RepoRoot string + Modules []string + ExcludedModules []string // modules filtered out by per-task paths; informational + Tasks []PlannedTask +} + +// PlannedTask is a task combined with its resolved module set. +type PlannedTask struct { + Task config.Task + Modules []string // empty slice means "run once with no module scope" + Selected bool + SkipReason string // populated when Selected is false or Modules is empty + CleanWorktree bool + Parallel bool +} + +// BuildPlan computes the plan without side effects. changedFiles and modules +// come from discover. +func BuildPlan(cfg config.Config, repoRoot string, changedFiles, modules []string, selected map[string]bool) Plan { + plan := Plan{ + RepoRoot: repoRoot, + Modules: modules, + } + + cleanDefault := cfg.CleanWorktreeEnabled() + + for i := range cfg.Tasks { + // Range by index + pointer to avoid copying the (potentially large) + // Task struct on every iteration; PlannedTask.Task takes its own + // copy below so callers can mutate the plan without affecting cfg. + t := &cfg.Tasks[i] + planned := PlannedTask{ + Task: *t, + Selected: true, + CleanWorktree: t.CleanWorktreeEnabled(cleanDefault), + Parallel: t.ParallelEnabled(), + } + + // Apply selection filter (nil means "take default_selected"). + if selected != nil { + if _, ok := selected[t.Name]; !ok { + planned.Selected = false + planned.SkipReason = "not selected" + } + } else if !t.DefaultSelected { + planned.Selected = false + planned.SkipReason = "not default_selected" + } + + // Apply per-task path filter to decide which files (and thus + // which modules) this task cares about. + taskFiles := discover.FilterByPaths(changedFiles, t.Paths, t.PathsIgnore) + if (len(t.Paths) > 0 || len(t.PathsIgnore) > 0) && len(taskFiles) == 0 { + planned.Selected = false + if planned.SkipReason == "" { + planned.SkipReason = "no files match task paths" + } + } + + if t.PerModule { + // Intersect the global module list with modules that own at + // least one taskFiles entry. When no path filter is set, + // taskFiles is the full file list, so the intersection equals + // the global module set. When path filters are set but there + // is no marker-derived module (e.g. tests using a synthetic + // module list), we still want to respect the task's filtered + // file set by keeping the global modules that "own" those + // files via prefix match. + mods := discover.Modules(repoRoot, taskFiles, cfg.ModuleMarkers) + if len(mods) == 0 && planned.Selected { + // Fall back: keep only global modules that have at least + // one taskFile under them. + mods = intersectModulesWithFiles(modules, taskFiles) + } + planned.Modules = mods + if planned.Selected && len(mods) == 0 { + planned.Selected = false + if planned.SkipReason == "" { + planned.SkipReason = "no modules matched" + } + } + } else { + // Non-per-module tasks run once without a module scope. + planned.Modules = []string{""} + } + + plan.Tasks = append(plan.Tasks, planned) + } + return plan +} + +// Options configures a Run invocation. +type Options struct { + Executor Executor + Git *git.Runner + Sink Sink + Version string + UpdateNotice string + MaxParallelism int // 0 == NumCPU + CleanWorktree bool // whether to stash unstaged changes around the run + DryRun bool // if true, emit events but do not execute commands +} + +// Result aggregates the outcome of a Run. +type Result struct { + Succeeded int + Failed int + ExitCode int + // FailedTasks is the names of tasks that ended in TaskStatusFailed, + // in selection order. Empty when Failed == 0. + FailedTasks []string +} + +// Run executes the plan's selected tasks and emits events to opts.Sink. +// +// Run never returns an error today; the (Result, error) shape is preserved so +// future failure modes (e.g. unrecoverable stash errors) can be surfaced +// without changing every caller. +func Run(ctx context.Context, plan Plan, opts Options) (Result, error) { + if opts.Sink == nil { + opts.Sink = DiscardSink{} + } + sink := opts.Sink + exec := opts.Executor + if exec == nil { + exec = BashExecutor{} + } + maxPar := opts.MaxParallelism + if maxPar <= 0 { + maxPar = runtime.NumCPU() + } + + emitRunStarted(sink, plan, opts) + + stash := beginStash(opts, sink) + defer endStash(stash, sink, opts.Git) + + var result Result + for i := range plan.Tasks { + pt := &plan.Tasks[i] + if !pt.Selected { + sink.Emit(Event{ + Kind: EventTaskCompleted, + Time: time.Now(), + Task: pt.Task.Name, + Status: TaskStatusSkipped, + Message: pt.SkipReason, + }) + continue + } + runOneTask(ctx, pt, plan.RepoRoot, opts, exec, sink, maxPar, &result) + } + + if result.Failed > 0 { + result.ExitCode = 1 + } + sink.Emit(Event{ + Kind: EventRunCompleted, + Time: time.Now(), + Succeeded: result.Succeeded, + Failed: result.Failed, + ExitCode: result.ExitCode, + FailedTasks: result.FailedTasks, + }) + return result, nil +} + +// emitRunStarted publishes the EventRunStarted event with selected task names. +func emitRunStarted(sink Sink, plan Plan, opts Options) { + taskNames := make([]string, 0, len(plan.Tasks)) + for i := range plan.Tasks { + if plan.Tasks[i].Selected { + taskNames = append(taskNames, plan.Tasks[i].Task.Name) + } + } + sink.Emit(Event{ + Kind: EventRunStarted, + Time: time.Now(), + Version: opts.Version, + Modules: plan.Modules, + Tasks: taskNames, + RepoRoot: plan.RepoRoot, + Message: opts.UpdateNotice, + }) +} + +// beginStash optionally stashes the worktree before the run; emits a notice on +// failure and returns nil so the run continues without stash protection. +func beginStash(opts Options, sink Sink) *git.Stash { + if !opts.CleanWorktree || opts.Git == nil || opts.DryRun { + return nil + } + s, err := opts.Git.Push() + if err != nil { + sink.Emit(Event{ + Kind: EventNotice, + Time: time.Now(), + Message: "stash failed; continuing without clean_worktree: " + err.Error(), + }) + return nil + } + return s +} + +// endStash pops a previously created stash, emitting a notice if restore +// fails. A failed pop typically means `stage_output` added files that +// conflicted with the user's own unstaged hunks; the original work is still +// recoverable from `git stash list`. +func endStash(stash *git.Stash, sink Sink, _ *git.Runner) { + if stash == nil { + return + } + if err := stash.Pop(); err != nil { + sink.Emit(Event{ + Kind: EventNotice, + Time: time.Now(), + Message: "failed to restore stash: " + err.Error() + + " (your unstaged changes are preserved; run `git stash list`, resolve any conflicts, then `git stash pop`)", + }) + } +} + +// snapshotTracked records the set of tracked files for a fresh "before" +// baseline. Used per-task so that stage_output only stages files that this +// task created, not files generated by an earlier task in the same run. +func snapshotTracked(opts Options) map[string]struct{} { + if opts.Git == nil || opts.DryRun { + return nil + } + snap, err := opts.Git.TrackedFiles() + if err != nil { + return nil + } + return snap +} + +// runOneTask executes a single planned task (possibly across modules) and +// updates result with success/failure counts. +func runOneTask(ctx context.Context, pt *PlannedTask, repoRoot string, opts Options, + exec Executor, sink Sink, maxPar int, result *Result) { + taskStart := time.Now() + + // Snapshot the tracked file set immediately before each task with + // stage_output so generated files from a previous task aren't + // mis-attributed here. + var beforeTracked map[string]struct{} + if pt.Task.StageOutput && opts.Git != nil && !opts.DryRun { + beforeTracked = snapshotTracked(opts) + } + + var taskFailed bool + if pt.Parallel && len(pt.Modules) > 1 && !opts.DryRun { + taskFailed = runParallel(ctx, pt, repoRoot, opts, exec, sink, maxPar) + } else { + taskFailed = runSequential(ctx, pt, repoRoot, opts, exec, sink) + } + + status := TaskStatusDone + if taskFailed { + status = TaskStatusFailed + result.Failed++ + result.FailedTasks = append(result.FailedTasks, pt.Task.Name) + } else { + result.Succeeded++ + } + sink.Emit(Event{ + Kind: EventTaskCompleted, + Time: time.Now(), + Task: pt.Task.Name, + Status: status, + DurationMs: time.Since(taskStart).Milliseconds(), + }) + + if !taskFailed && pt.Task.StageOutput && opts.Git != nil && !opts.DryRun { + if err := stageGenerated(opts.Git, pt.Task.OutputPatterns, beforeTracked, pt.Modules); err != nil { + sink.Emit(Event{ + Kind: EventNotice, + Time: time.Now(), + Task: pt.Task.Name, + Message: "staging output failed: " + err.Error(), + }) + } + } +} + +// runSequential executes pt's modules one by one, fail-fast on the first +// error. When fail-fast triggers, remaining modules are reported as skipped +// so consumers see a terminating event for every module that was queued. +// Returns true if any module failed. +func runSequential(ctx context.Context, pt *PlannedTask, repoRoot string, opts Options, exec Executor, sink Sink) bool { + for i, module := range pt.Modules { + if err := runOne(ctx, pt, module, repoRoot, opts, exec, sink); err != nil { + emitModuleSkips(sink, pt.Task.Name, pt.Modules[i+1:], "fail-fast: sibling module failed") + return true + } + } + return false +} + +// runParallel fans per-module runs out with bounded concurrency. The first +// failure short-circuits subsequent work (fail-fast is preserved); modules +// that never started or aborted before runOne report a skip notice so the +// event stream stays in balance with EventTaskStarted/EventTaskCompleted +// expectations downstream. Returns true if any module failed. +func runParallel(ctx context.Context, pt *PlannedTask, repoRoot string, opts Options, exec Executor, sink Sink, maxPar int) bool { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + sem := make(chan struct{}, maxPar) + var wg sync.WaitGroup + var mu sync.Mutex + var failed bool + skipped := make(map[string]bool, len(pt.Modules)) + + modules := make([]string, len(pt.Modules)) + copy(modules, pt.Modules) + sort.Strings(modules) // stable event ordering for tests + + for _, module := range modules { + mu.Lock() + if failed { + skipped[module] = true + mu.Unlock() + continue + } + mu.Unlock() + + wg.Add(1) + sem <- struct{}{} + go func(module string) { + defer wg.Done() + defer func() { <-sem }() + + // Re-check failure state after acquiring the semaphore so + // queued goroutines drop out as soon as a sibling fails. + mu.Lock() + abort := failed + if abort { + skipped[module] = true + } + mu.Unlock() + if abort { + return + } + + if err := runOne(ctx, pt, module, repoRoot, opts, exec, sink); err != nil { + mu.Lock() + failed = true + mu.Unlock() + cancel() + } + }(module) + } + wg.Wait() + if failed { + // Emit deterministic skip notices in module order. + var skippedList []string + for _, m := range modules { + if skipped[m] { + skippedList = append(skippedList, m) + } + } + emitModuleSkips(sink, pt.Task.Name, skippedList, "fail-fast: sibling module failed") + } + return failed +} + +// emitModuleSkips emits a skip-flavored notice per module so consumers know +// these per-module runs were aborted by fail-fast rather than silently +// dropped. Notices are used (rather than synthetic task_completed events) +// because EventTaskCompleted is reserved for the per-task aggregate. +func emitModuleSkips(sink Sink, task string, modules []string, reason string) { + for _, m := range modules { + sink.Emit(Event{ + Kind: EventNotice, + Time: time.Now(), + Task: task, + Module: m, + Message: reason, + }) + } +} + +// runOne executes pt on a single module, emitting task_started, line events, +// and either nothing (caller emits task_completed) or an error message. +func runOne(ctx context.Context, pt *PlannedTask, module, repoRoot string, opts Options, exec Executor, sink Sink) error { + vars := NewTemplateVars(repoRoot, module) + command := vars.Expand(pt.Task.Command) + + workingDir := pt.Task.WorkingDir + if workingDir != "" { + workingDir = vars.Expand(workingDir) + } else if pt.Task.PerModule && module != "" { + workingDir = vars.ModuleRoot + } else { + workingDir = vars.RepoRoot + } + + // Emit task_started after template expansion so the event carries the + // resolved command and working directory. Consumers reading a failure + // transcript can then reproduce the exact invocation without also + // reading .prenup.yaml or knowing how prenup expands templates. + sink.Emit(Event{ + Kind: EventTaskStarted, + Time: time.Now(), + Task: pt.Task.Name, + Module: module, + Command: command, + WorkingDir: workingDir, + }) + + if opts.DryRun { + sink.Emit(Event{ + Kind: EventLine, + Time: time.Now(), + Task: pt.Task.Name, + Module: module, + Stream: StreamStdout, + Text: fmt.Sprintf("[dry-run] would run: %s (in %s)", command, workingDir), + }) + return nil + } + + err := exec.Run(ctx, command, workingDir, pt.Task.Env, func(stream Stream, text string) { + sink.Emit(Event{ + Kind: EventLine, + Time: time.Now(), + Task: pt.Task.Name, + Module: module, + Stream: stream, + Text: text, + }) + }) + if err != nil { + // Prefix with [prenup] so consumers can distinguish prenup's own + // synthetic failure attribution from a stderr line the user's + // script happened to print (e.g. a test harness reporting on a + // child process). The Message field carries the unprefixed error + // for programmatic consumers. + sink.Emit(Event{ + Kind: EventLine, + Time: time.Now(), + Task: pt.Task.Name, + Module: module, + Stream: StreamStderr, + Text: "[prenup] command failed: " + err.Error(), + Message: err.Error(), + }) + } + return err +} + +// intersectModulesWithFiles returns the subset of modules that own at least +// one of files via a path-prefix match. Used as a fallback when the +// configured module_markers aren't present on disk (e.g. test scaffolding) +// but the caller still provides a plausible module list. +// +// "." is treated as the repo root, which owns every file. +func intersectModulesWithFiles(modules, files []string) []string { + out := make([]string, 0, len(modules)) + for _, m := range modules { + if m == "." { + out = append(out, m) + continue + } + for _, f := range files { + if hasPathPrefix(f, m) { + out = append(out, m) + break + } + } + } + return out +} + +// hasPathPrefix reports whether s is exactly prefix or sits below prefix in +// the path hierarchy. Operates on slash-separated paths (the form git uses). +func hasPathPrefix(s, prefix string) bool { + if prefix == "" || prefix == "." { + return true + } + return s == prefix || strings.HasPrefix(s, prefix+"/") +} diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go new file mode 100644 index 0000000..0ece969 --- /dev/null +++ b/internal/runner/runner_test.go @@ -0,0 +1,264 @@ +package runner_test + +import ( + "context" + "errors" + "sync" + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/runner" + "github.com/c2fo/prenup/internal/runner/mocks" +) + +// RunnerTestSuite groups end-to-end behavioral tests for the runner: its +// plan-building rules and its event-emission contract during execution. +// Tests are written against the public package (runner_test) so the +// suite exercises the same surface a downstream consumer would. +type RunnerTestSuite struct { + suite.Suite +} + +// recordingSink wraps a mockery-generated Sink mock with a permissive +// catch-all EXPECT() that captures every emitted event into a slice +// the test can assert against. The runner emits many events per run +// (started, line, completed for each task plus run-level bookends); +// strict per-event expectations would be brittle and would obscure +// the actual behavior under test. Tests that care about a specific +// event walk the captured slice instead. +type recordingSink struct { + mock *mocks.Sink + mu sync.Mutex + events []runner.Event +} + +func (s *RunnerTestSuite) newRecordingSink() *recordingSink { + rs := &recordingSink{mock: mocks.NewSink(s.T())} + rs.mock.EXPECT(). + Emit(mock.Anything). + Run(func(ev runner.Event) { + rs.mu.Lock() + defer rs.mu.Unlock() + rs.events = append(rs.events, ev) + }). + Return(). + Maybe() + rs.mock.EXPECT().Close().Return(nil).Maybe() + return rs +} + +func (rs *recordingSink) snapshot() []runner.Event { + rs.mu.Lock() + defer rs.mu.Unlock() + out := make([]runner.Event, len(rs.events)) + copy(out, rs.events) + return out +} + +// stubExecutor returns a mockery Executor pre-wired so any Run call +// returns runResult, optionally invoking the runner-supplied onLine +// callback with stdoutLine first. Tests that need different per-call +// behavior wire the EXPECT chain themselves. +func (s *RunnerTestSuite) stubExecutor(stdoutLine string, runResult error) (*mocks.Executor, *atomicCallLog) { + exec := mocks.NewExecutor(s.T()) + calls := &atomicCallLog{} + exec.EXPECT(). + Run(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + RunAndReturn(func(_ context.Context, command, workingDir string, env map[string]string, onLine func(runner.Stream, string)) error { + calls.add(command, workingDir, env) + if stdoutLine != "" { + onLine(runner.StreamStdout, stdoutLine) + } + return runResult + }). + Maybe() + return exec, calls +} + +// atomicCallLog records executor invocations from concurrent runner +// goroutines without forcing tests to manage their own mutex. +type atomicCallLog struct { + mu sync.Mutex + calls []executorCall +} + +type executorCall struct { + command string + workingDir string + env map[string]string +} + +func (l *atomicCallLog) add(command, workingDir string, env map[string]string) { + l.mu.Lock() + defer l.mu.Unlock() + l.calls = append(l.calls, executorCall{command: command, workingDir: workingDir, env: env}) +} + +func (l *atomicCallLog) len() int { + l.mu.Lock() + defer l.mu.Unlock() + return len(l.calls) +} + +func (s *RunnerTestSuite) TestSequentialSuccess() { + cfg := config.Config{ + Version: 2, + ModuleMarkers: []string{"go.mod"}, + Tasks: []config.Task{ + {Name: "A", Command: "cmd-a", DefaultSelected: true, PerModule: true}, + {Name: "B", Command: "cmd-b", DefaultSelected: true, PerModule: true}, + }, + } + plan := runner.BuildPlan(cfg, "/repo", []string{"pkg/foo/x.go"}, []string{"pkg/foo"}, nil) + s.Require().Len(plan.Tasks, 2) + s.True(plan.Tasks[0].Selected) + + exec, _ := s.stubExecutor("", nil) + sink := s.newRecordingSink() + opts := runner.Options{ + Executor: exec, + Sink: sink.mock, + Version: "v2.0.0", + MaxParallelism: 1, + CleanWorktree: false, + } + result, err := runner.Run(context.Background(), plan, opts) + s.Require().NoError(err) + s.Equal(0, result.ExitCode) + s.Equal(2, result.Succeeded) + s.Equal(0, result.Failed) + + events := sink.snapshot() + s.Require().NotEmpty(events) + s.Equal(runner.EventRunStarted, events[0].Kind, "first event must be run_started") + s.Equal(runner.EventRunCompleted, events[len(events)-1].Kind, "last event must be run_completed") +} + +// TestFailFastWithinTaskAcrossModules pins two intertwined behaviors: +// 1. when the first module of a per-module task fails, the runner +// does not try the remaining modules of that task; and +// 2. the synthetic failure-attribution stderr line carries a +// [prenup] prefix in Text but keeps the unprefixed cause in +// Message, so a programmatic consumer can distinguish prenup's +// own attribution from a stderr line the user's command happened +// to emit. +func (s *RunnerTestSuite) TestFailFastWithinTaskAcrossModules() { + cfg := config.Config{ + Version: 2, + ModuleMarkers: []string{"go.mod"}, + Tasks: []config.Task{ + {Name: "Lint", Command: "lint", DefaultSelected: true, PerModule: true}, + }, + } + modules := []string{"a", "b", "c"} + plan := runner.BuildPlan(cfg, "/repo", []string{"a/x.go", "b/y.go", "c/z.go"}, modules, nil) + plan.Tasks[0].Modules = modules + + exec, calls := s.stubExecutor("", errors.New("boom")) + sink := s.newRecordingSink() + opts := runner.Options{Executor: exec, Sink: sink.mock, MaxParallelism: 1, CleanWorktree: false} + + result, err := runner.Run(context.Background(), plan, opts) + s.Require().NoError(err) + s.Equal(1, result.ExitCode) + s.Equal(1, result.Failed) + s.Equal(1, calls.len(), "fail-fast must stop after the first failing module") + + var foundFailureLine bool + // runner.Event is large (~320B); iterate by index per gocritic's + // rangeValCopy to avoid copying every event into the loop variable. + events := sink.snapshot() + for i := range events { + ev := &events[i] + if ev.Kind == runner.EventLine && ev.Stream == runner.StreamStderr && ev.Message == "boom" { + s.Equal("[prenup] command failed: boom", ev.Text, + "runner-synthesized failure line must be tagged with [prenup]") + foundFailureLine = true + break + } + } + s.True(foundFailureLine, "expected a synthetic failure-attribution line on EventLine/stderr") +} + +func (s *RunnerTestSuite) TestBuildPlanAppliesPathFilter() { + cfg := config.Config{ + Version: 2, + ModuleMarkers: []string{"go.mod"}, + Tasks: []config.Task{ + { + Name: "Go tests", Command: "go test", DefaultSelected: true, PerModule: true, + Paths: []string{"**/*.go"}, + }, + { + Name: "Docs", Command: "mkdocs", DefaultSelected: true, + Paths: []string{"docs/**/*.md"}, + }, + }, + } + plan := runner.BuildPlan(cfg, "/repo", []string{"pkg/foo/x.go", "README.txt"}, []string{"pkg/foo"}, nil) + s.True(plan.Tasks[0].Selected, "Go tests must be selected when a .go file changed") + s.False(plan.Tasks[1].Selected, "Docs must be skipped when no .md file changed") + s.Contains(plan.Tasks[1].SkipReason, "no files match task paths") +} + +func (s *RunnerTestSuite) TestBuildPlanSelectionMap() { + cfg := config.Config{ + Version: 2, + ModuleMarkers: []string{"go.mod"}, + Tasks: []config.Task{ + {Name: "A", Command: "a", DefaultSelected: true, PerModule: true}, + {Name: "B", Command: "b", DefaultSelected: true, PerModule: true}, + }, + } + plan := runner.BuildPlan(cfg, "/repo", []string{"m/x.go"}, []string{"m"}, + map[string]bool{"A": true}) + s.True(plan.Tasks[0].Selected, "A must respect explicit selection=true") + s.False(plan.Tasks[1].Selected, "B must respect explicit absence (no implicit selection)") +} + +// TestTemplateExpand is a pure-function check; no executor/sink needed. +// It uses table-driven cases to keep all template variables on display. +func (s *RunnerTestSuite) TestTemplateExpand() { + cases := []struct { + name string + repo string + module string + input string + want string + }{ + { + name: "all four vars", + repo: "/repo", + module: "services/auth", + input: "run {{.module_name}} in {{.module_root}} from {{.repo_root}} path {{.module_path}}", + want: "run auth in /repo/services/auth from /repo path services/auth", + }, + { + name: "module_name is the leaf of module_path", + repo: "/repo", + module: "a/b/c/leaf", + input: "{{.module_name}}", + want: "leaf", + }, + { + name: "no template vars passes through", + repo: "/repo", + module: "x", + input: "literal command", + want: "literal command", + }, + } + for _, tc := range cases { + s.Run(tc.name, func() { + v := runner.NewTemplateVars(tc.repo, tc.module) + s.Equal(tc.want, v.Expand(tc.input)) + }) + } +} + +func TestRunnerSuite(t *testing.T) { + suite.Run(t, new(RunnerTestSuite)) +} diff --git a/internal/runner/stage.go b/internal/runner/stage.go new file mode 100644 index 0000000..e8b52b7 --- /dev/null +++ b/internal/runner/stage.go @@ -0,0 +1,74 @@ +package runner + +import ( + "github.com/bmatcuk/doublestar/v4" + + "github.com/c2fo/prenup/internal/git" +) + +// stageGenerated stages files that newly appeared (vs beforeTracked), match +// any of the doublestar patterns, and (for per-module tasks) live under one +// of the task's modules. Pre-existing unstaged changes are never promoted. +// +// modules may be nil/empty (non per-module tasks) or contain "" / "." to mean +// "scope to the whole repo". A non-empty list of real module paths restricts +// staging to files that live under those modules so a task that ran in module +// A cannot accidentally stage files under module B. +func stageGenerated(gitRunner *git.Runner, patterns []string, beforeTracked map[string]struct{}, modules []string) error { + if len(patterns) == 0 { + return nil + } + after, err := gitRunner.PorcelainStatus() + if err != nil { + return err + } + + scoped := make([]string, 0, len(modules)) + for _, m := range modules { + if m == "" || m == "." { + // Wildcard scope: a single empty entry signals "match anywhere" + // to underModule below. + scoped = nil + break + } + scoped = append(scoped, m) + } + + var toStage []string + for _, f := range after { + if _, existed := beforeTracked[f]; existed { + continue + } + if !matchesAny(patterns, f) { + continue + } + if !underModules(f, scoped) { + continue + } + toStage = append(toStage, f) + } + return gitRunner.Add(toStage) +} + +// underModules returns true when f lives under one of modules. An empty +// modules slice means "match anywhere" (no per-module restriction). +func underModules(f string, modules []string) bool { + if len(modules) == 0 { + return true + } + for _, m := range modules { + if hasPathPrefix(f, m) { + return true + } + } + return false +} + +func matchesAny(patterns []string, path string) bool { + for _, p := range patterns { + if ok, err := doublestar.Match(p, path); err == nil && ok { + return true + } + } + return false +} diff --git a/internal/runner/stage_test.go b/internal/runner/stage_test.go new file mode 100644 index 0000000..a5c11a1 --- /dev/null +++ b/internal/runner/stage_test.go @@ -0,0 +1,114 @@ +package runner + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/c2fo/prenup/internal/git" +) + +// newStageRepo creates a fresh git repo with one committed README so we have +// a known "tracked before" baseline to compare against. +func newStageRepo(t *testing.T) (string, *git.Runner) { + t.Helper() + dir := t.TempDir() + mustGit(t, dir, "init", "-b", "main") + mustGit(t, dir, "config", "user.email", "test@example.com") + mustGit(t, dir, "config", "user.name", "test") + mustGit(t, dir, "config", "commit.gpgsign", "false") + require.NoError(t, os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi\n"), 0o600)) + mustGit(t, dir, "add", "README.md") + mustGit(t, dir, "commit", "-m", "initial") + return dir, git.New(dir) +} + +func mustGit(t *testing.T, dir string, args ...string) { + t.Helper() + cmd := exec.Command("git", args...) //nolint:gosec // G204: git binary is fixed in tests. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + require.NoErrorf(t, err, "git %s: %s", strings.Join(args, " "), string(out)) +} + +// TestStageGeneratedSkipsPreexistingFiles guarantees that stage_output never +// promotes a file that was already tracked before the task ran -- otherwise +// users' in-progress edits to existing generated files would be silently +// added to the index. +func TestStageGeneratedSkipsPreexistingFiles(t *testing.T) { + t.Parallel() + dir, r := newStageRepo(t) + require.NoError(t, os.WriteFile(filepath.Join(dir, "gen.txt"), []byte("v1\n"), 0o600)) + mustGit(t, dir, "add", "gen.txt") + mustGit(t, dir, "commit", "-m", "add gen") + + before, err := r.TrackedFiles() + require.NoError(t, err) + + require.NoError(t, os.WriteFile(filepath.Join(dir, "gen.txt"), []byte("v2 unstaged\n"), 0o600)) + + require.NoError(t, stageGenerated(r, []string{"gen.txt"}, before, nil)) + + staged, err := r.StagedFiles() + require.NoError(t, err) + assert.Empty(t, staged, "pre-existing file should not be auto-staged") +} + +// TestStageGeneratedAddsNewMatchingFiles is the happy path: a brand-new file +// matching the patterns is staged. +func TestStageGeneratedAddsNewMatchingFiles(t *testing.T) { + t.Parallel() + dir, r := newStageRepo(t) + before, err := r.TrackedFiles() + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(dir, "out.txt"), []byte("new\n"), 0o600)) + + require.NoError(t, stageGenerated(r, []string{"out.txt"}, before, nil)) + + staged, err := r.StagedFiles() + require.NoError(t, err) + assert.Equal(t, []string{"out.txt"}, staged) +} + +// TestStageGeneratedRespectsModuleScope ensures a per-module task that ran +// in module A cannot accidentally stage files generated in module B even +// when both happen to match the patterns. +func TestStageGeneratedRespectsModuleScope(t *testing.T) { + t.Parallel() + dir, r := newStageRepo(t) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "a"), 0o750)) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "b"), 0o750)) + before, err := r.TrackedFiles() + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(dir, "a", "out.txt"), []byte("a\n"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "b", "out.txt"), []byte("b\n"), 0o600)) + + require.NoError(t, stageGenerated(r, []string{"**/out.txt"}, before, []string{"a"})) + + staged, err := r.StagedFiles() + require.NoError(t, err) + assert.Equal(t, []string{"a/out.txt"}, staged) +} + +// TestStageGeneratedDotModuleStagesEverywhere covers the "wildcard" module +// scope (e.g. non per-module task synthesized to "."). +func TestStageGeneratedDotModuleStagesEverywhere(t *testing.T) { + t.Parallel() + dir, r := newStageRepo(t) + require.NoError(t, os.MkdirAll(filepath.Join(dir, "x"), 0o750)) + before, err := r.TrackedFiles() + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(dir, "top.txt"), []byte("t\n"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "x", "deep.txt"), []byte("d\n"), 0o600)) + + require.NoError(t, stageGenerated(r, []string{"**/*.txt"}, before, []string{"."})) + + staged, err := r.StagedFiles() + require.NoError(t, err) + assert.ElementsMatch(t, []string{"top.txt", "x/deep.txt"}, staged) +} diff --git a/internal/runner/template.go b/internal/runner/template.go new file mode 100644 index 0000000..0929b27 --- /dev/null +++ b/internal/runner/template.go @@ -0,0 +1,50 @@ +package runner + +import ( + "path/filepath" + "strings" +) + +// TemplateVars holds the values substituted into command and working_dir. +type TemplateVars struct { + RepoRoot string + ModuleRoot string + ModulePath string + ModuleName string +} + +// NewTemplateVars computes the template variables for module (a path relative +// to repoRoot). A module of "." yields the repo itself; for global tasks, call +// with module = "". +func NewTemplateVars(repoRoot, module string) TemplateVars { + if module == "" { + return TemplateVars{RepoRoot: repoRoot} + } + return TemplateVars{ + RepoRoot: repoRoot, + ModuleRoot: filepath.Join(repoRoot, module), + ModulePath: module, + ModuleName: filepath.Base(module), + } +} + +// Expand replaces the supported template variables in s. +// Supported: {{.repo_root}}, {{.module_root}}, {{.module_path}}, {{.module_name}}. +// Unknown variables are left untouched. +func (v TemplateVars) Expand(s string) string { + pairs := map[string]string{ + "{{.repo_root}}": v.RepoRoot, + "{{.module_root}}": v.ModuleRoot, + "{{.module_path}}": v.ModulePath, + "{{.module_name}}": v.ModuleName, + // Backward-compatible alias that v1 users may have in configs. + "{{.module}}": v.ModuleName, + } + for k, val := range pairs { + if val == "" { + continue + } + s = strings.ReplaceAll(s, k, val) + } + return s +} diff --git a/internal/ui/agent.go b/internal/ui/agent.go new file mode 100644 index 0000000..5987ae5 --- /dev/null +++ b/internal/ui/agent.go @@ -0,0 +1,72 @@ +package ui + +// This file holds the canonical strings prenup emits to AI agents and other +// non-human consumers so the markdown digest and the NDJSON stream stay in +// lockstep. Humans get the Bubble Tea TUI and the chrome it provides, so +// these strings are intentionally absent from the human renderer. +// +// When you change one of these constants, please also: +// - re-run `go test ./internal/ui/...` (golden assertions key off them) +// - update prenup/docs/SCHEMA.md if you change AgentHintSchema or the +// shape of the agent_hint event. + +// Tool is the project name as it should appear to consumers. Stable. +const Tool = "prenup" + +// HomepageURL points readers at human-facing docs. Stable. +const HomepageURL = "https://github.com/c2fo/prenup" + +// Description is a one-sentence "what is this" so an agent that has never +// heard of prenup can orient itself from a single line of output. +const Description = "Prenup is a Git pre-commit hook runner that executes " + + "configurable quality checks (tests, linters, generators) scoped to " + + "the modules touched by the in-flight commit." + +// HookContextNote tells consumers that this output came from a Git hook -- +// not from `git` itself -- so they don't misattribute failures to git. +const HookContextNote = "This output is produced by the prenup pre-commit " + + "hook, not by `git` itself. A non-zero exit means the hook blocked the " + + "commit; the commit was not created." + +// JSONHint advertises the structured output mode. Shown only in markdown. +const JSONHint = "For machine-readable output, run with " + + "`--output json` or set `PRENUP_OUTPUT=json` in the environment." + +// FailureBypassHint explains how to retry or bypass after a failed run. +// Used in the markdown digest's "Next steps" block and surfaced verbatim +// in the agent_hint event so JSON consumers see the same guidance. +const FailureBypassHint = "Re-run `git commit` after fixing the issue, or " + + "use `git commit --no-verify` to bypass the hook for a single commit. " + + "Configuration lives in `.prenup.yaml` at the repository root." + +// AgentHintSchema is the version of the agent_hint event payload. Bump +// when the shape of the strings or the surrounding event fields changes +// in a way that would surprise an existing consumer. +const AgentHintSchema = "1" + +// DevVersion is the literal Version string an unbuilt or `go install`-from- +// source binary reports. Anything else is taken to be a real semver-like +// release identifier. +const DevVersion = "dev" + +// VersionLabel turns a raw version token into a human- and agent-friendly +// label that makes the field's role obvious. A pattern-matching consumer +// scanning for `Prenup dev` would otherwise have to guess whether `dev` +// is a build identifier, a status, or part of a task name. Examples: +// +// "v2.0.1" -> "v2.0.1" +// "dev" -> "dev (development build, not a tagged release)" +// "" -> "unknown" +// +// Wire/JSON consumers receive the raw token in Event.Version unchanged; +// this helper only formats it for human-readable contexts. +func VersionLabel(version string) string { + switch version { + case "": + return "unknown" + case DevVersion: + return DevVersion + " (development build, not a tagged release)" + default: + return version + } +} diff --git a/internal/ui/human/runner_ui.go b/internal/ui/human/runner_ui.go new file mode 100644 index 0000000..277ea21 --- /dev/null +++ b/internal/ui/human/runner_ui.go @@ -0,0 +1,331 @@ +package human + +import ( + "fmt" + "sort" + "strings" + "sync" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + + "github.com/c2fo/prenup/internal/runner" +) + +// EventChannelSink is a runner.Sink that pushes every event onto a buffered +// channel. The runner UI consumes the channel via a tea.Cmd loop, which keeps +// rendering and event processing on the same goroutine and avoids relying on +// the program's internal Send queue for high-throughput line streams. +// +// Concurrency: +// - Emit may be called from any number of goroutines (parallel per-module +// task runs) at the same time. +// - Once Close has been called, every subsequent Emit returns immediately +// instead of panicking with "send on closed channel" or pinning the +// producer on a full buffer. The underlying event channel is intentionally +// never closed, because doing so would race with producers that are still +// in Emit; instead the consumer loop watches Done() to know when to quit. +// - Close is safe to call multiple times; only the first call has any +// effect. +type EventChannelSink struct { + ch chan runner.Event + done chan struct{} + closer sync.Once +} + +// NewEventChannelSink returns a sink with a generously-sized buffer so the +// runner goroutine rarely blocks waiting for the UI to render. +func NewEventChannelSink() *EventChannelSink { + return &EventChannelSink{ + ch: make(chan runner.Event, 1024), + done: make(chan struct{}), + } +} + +// Emit forwards ev to the consumer channel. After Close has been called the +// event is dropped silently. Late notices from deferred cleanup (stash pop, +// etc.) emitted after EventRunCompleted must not crash the process. +func (s *EventChannelSink) Emit(ev runner.Event) { + select { + case <-s.done: + return + default: + } + select { + case <-s.done: + case s.ch <- ev: + } +} + +// Close marks the sink as closed exactly once. In-flight Emit calls observe +// the closed state and drop their event; the consumer loop should select on +// both Channel() and Done() and exit when Done fires. +func (s *EventChannelSink) Close() error { + s.closer.Do(func() { close(s.done) }) + return nil +} + +// Channel exposes the underlying buffered event channel. +// +// The channel is intentionally never closed; consumers must also watch Done() +// to know when to terminate. Closing the channel would race with producer +// goroutines still inside Emit and could panic with "send on closed channel". +func (s *EventChannelSink) Channel() <-chan runner.Event { return s.ch } + +// Done returns a channel that is closed when Close is called. Consumers select +// on both Channel() and Done() to terminate cleanly. +func (s *EventChannelSink) Done() <-chan struct{} { return s.done } + +// eventMsg wraps a runner event for delivery to the Bubble Tea Update loop. +type eventMsg struct{ ev runner.Event } + +// channelClosedMsg signals the event channel was closed (run is fully done). +type channelClosedMsg struct{} + +// nextEventCmd returns a tea.Cmd that blocks waiting for one event from ch +// (or for done to fire, signaling shutdown), then re-issues itself in the +// model's Update so the loop continues. +func nextEventCmd(ch <-chan runner.Event, done <-chan struct{}) tea.Cmd { + return func() tea.Msg { + select { + case ev := <-ch: + return eventMsg{ev: ev} + case <-done: + // Drain any remaining buffered events so the final summary is + // captured before quitting. + select { + case ev := <-ch: + return eventMsg{ev: ev} + default: + return channelClosedMsg{} + } + } + } +} + +// taskUIState holds the per-task display data. +type taskUIState struct { + name string + status string // pending, running, done, error, skipped + modules map[string]string + duration int64 + note string +} + +// maxLogLines bounds the in-memory log buffer so streaming many thousands of +// lines does not turn viewport.SetContent into a quadratic operation. +const maxLogLines = 5000 + +// RunnerModel is the Bubble Tea model that renders runner events live. +type RunnerModel struct { + events <-chan runner.Event + done <-chan struct{} + version string + notice string + order []string + tasks map[string]*taskUIState + logLines []string + viewport viewport.Model + finished bool +} + +// NewRunnerModel constructs a RunnerModel that consumes events from sink. +// The model uses both the event channel and the sink's Done channel so it +// can shut down cleanly even when no terminating event arrives. +func NewRunnerModel(sink *EventChannelSink) *RunnerModel { + vp := viewport.New(80, 20) + vp.SetContent("Waiting for tasks...\n") + return &RunnerModel{ + events: sink.Channel(), + done: sink.Done(), + tasks: make(map[string]*taskUIState), + viewport: vp, + } +} + +// Init kicks off the event-pump command. Bubble Tea will continuously +// re-issue this command (via nextEventCmd) as long as events arrive. +func (m *RunnerModel) Init() tea.Cmd { return nextEventCmd(m.events, m.done) } + +// Update processes events and key/window messages. +func (m *RunnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + if s := msg.String(); s == "ctrl+c" || s == "q" { + return m, tea.Quit + } + case tea.WindowSizeMsg: + if msg.Width > 0 { + m.viewport.Width = msg.Width + } + if msg.Height > 0 { + h := msg.Height - (len(m.tasks) + 8) + if h < 4 { + h = 4 + } + m.viewport.Height = h + } + case eventMsg: + m.applyEvent(msg.ev) + if msg.ev.Kind == runner.EventRunCompleted { + m.finished = true + } + return m, nextEventCmd(m.events, m.done) + case channelClosedMsg: + return m, tea.Quit + } + var cmd tea.Cmd + m.viewport, cmd = m.viewport.Update(msg) + return m, cmd +} + +func (m *RunnerModel) applyEvent(ev runner.Event) { + switch ev.Kind { + case runner.EventRunStarted: + m.version = ev.Version + m.notice = ev.Message + for _, name := range ev.Tasks { + if _, ok := m.tasks[name]; !ok { + m.tasks[name] = &taskUIState{ + name: name, + status: "pending", + modules: make(map[string]string), + } + m.order = append(m.order, name) + } + } + case runner.EventTaskStarted: + t := m.ensureTask(ev.Task) + t.status = "running" + t.modules[ev.Module] = "running" + m.appendLog(fmt.Sprintf("> %s (%s)", ev.Task, ev.Module)) + case runner.EventLine: + line := ev.Text + if ev.Module != "" { + line = "[" + ev.Module + "] " + line + } + m.appendLog(line) + case runner.EventTaskCompleted: + t := m.ensureTask(ev.Task) + t.duration += ev.DurationMs + var moduleStatus string + switch ev.Status { + case runner.TaskStatusDone: + t.status = "done" + moduleStatus = "done" + case runner.TaskStatusFailed: + t.status = "error" + moduleStatus = "error" + case runner.TaskStatusSkipped: + t.status = "skipped" + moduleStatus = "skipped" + t.note = ev.Message + } + // Promote any modules left in "running" to the task's final status so + // the per-module display reflects completion. Modules that completed + // successfully but the task as a whole failed remain "running" until + // here; that's acceptable since fail-fast aborts the rest. + for mod, st := range t.modules { + if st == "running" { + t.modules[mod] = moduleStatus + } + } + case runner.EventNotice: + m.appendLog("notice: " + ev.Message) + } + m.viewport.SetContent(strings.Join(m.logLines, "\n")) + m.viewport.GotoBottom() +} + +// appendLog records line in the bounded log buffer. +func (m *RunnerModel) appendLog(line string) { + m.logLines = append(m.logLines, line) + if over := len(m.logLines) - maxLogLines; over > 0 { + // Drop the oldest 'over' lines. Keep the slice short to avoid the + // underlying array growing without bound. + m.logLines = append(m.logLines[:0], m.logLines[over:]...) + } +} + +func (m *RunnerModel) ensureTask(name string) *taskUIState { + t, ok := m.tasks[name] + if !ok { + t = &taskUIState{name: name, status: "pending", modules: make(map[string]string)} + m.tasks[name] = t + m.order = append(m.order, name) + } + return t +} + +// View renders the header, task checklist, and live log viewport. +func (m *RunnerModel) View() string { + headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#00FF00")) + warnStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFAA00")) + separatorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#444444")) + + var sb strings.Builder + sb.WriteString(headerStyle.Render("Prenup " + m.version)) + sb.WriteString("\n") + if m.notice != "" { + sb.WriteString(warnStyle.Render(m.notice)) + sb.WriteString("\n") + } + sb.WriteString("\n") + sb.WriteString(headerStyle.Render("Task Checklist:")) + sb.WriteString("\n\n") + + for _, name := range m.order { + t := m.tasks[name] + icon, color := statusIcon(t.status) + iconStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(color)) + fmt.Fprintf(&sb, " %s %s", iconStyle.Render(icon), t.name) + if t.duration > 0 { + fmt.Fprintf(&sb, " (%dms)", t.duration) + } + if t.note != "" { + sb.WriteString(" -- " + t.note) + } + sb.WriteByte('\n') + // When a task ran across multiple modules, show their per-module + // status under the task line so users can see fan-out progress. + if len(t.modules) > 1 { + modNames := make([]string, 0, len(t.modules)) + for name := range t.modules { + if name == "" { + continue + } + modNames = append(modNames, name) + } + sort.Strings(modNames) + for _, mod := range modNames { + modIcon, modColor := statusIcon(t.modules[mod]) + modStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(modColor)) + fmt.Fprintf(&sb, " %s %s\n", modStyle.Render(modIcon), mod) + } + } + } + + sb.WriteString("\n") + sb.WriteString(separatorStyle.Render("----------------------------------------")) + sb.WriteString("\n") + sb.WriteString(headerStyle.Render("Output:")) + sb.WriteString("\n") + sb.WriteString(m.viewport.View()) + return sb.String() +} + +func statusIcon(status string) (string, string) { + switch status { + case "running": + return "*", "#FFFF00" + case "done": + return "v", "#00FF00" + case "error": + return "x", "#FF0000" + case "skipped": + return "-", "#888888" + default: + return "o", "#444444" + } +} diff --git a/internal/ui/human/selection.go b/internal/ui/human/selection.go new file mode 100644 index 0000000..79540ae --- /dev/null +++ b/internal/ui/human/selection.go @@ -0,0 +1,187 @@ +// Package human renders the interactive Bubble Tea UIs: task selection and +// runner output. It is selected automatically when stdout is a TTY. +package human + +import ( + "errors" + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + + "github.com/c2fo/prenup/internal/config" +) + +// ErrCanceled signals the user quit the selection UI; the caller should block +// the commit with a non-zero exit. +var ErrCanceled = errors.New("canceled by user") + +// SelectionInput configures the selection UI. +type SelectionInput struct { + Version string + Notice string // update warning, shown in yellow + Modules []string + Tasks []config.Task + DefaultOnly bool // when true, skip the UI and return default_selected tasks +} + +// SelectionResult indicates the user's choice. +type SelectionResult struct { + Selected map[string]bool // task name -> selected + Skipped bool // user hit 's': run nothing, commit proceeds +} + +// SelectTasks runs the Bubble Tea selection UI. Returns ErrCanceled when the +// user quits with q/ctrl+c (the caller should block the commit). +func SelectTasks(in SelectionInput) (SelectionResult, error) { + if in.DefaultOnly { + sel := map[string]bool{} + for i := range in.Tasks { + if in.Tasks[i].DefaultSelected { + sel[in.Tasks[i].Name] = true + } + } + return SelectionResult{Selected: sel}, nil + } + + model := newSelectionModel(in) + prog := tea.NewProgram(model, tea.WithAltScreen()) + finalModel, err := prog.Run() + if err != nil { + return SelectionResult{}, fmt.Errorf("selection UI: %w", err) + } + m := finalModel.(*selectionModel) + if m.canceled { + return SelectionResult{}, ErrCanceled + } + if m.skipped { + return SelectionResult{Skipped: true}, nil + } + + sel := make(map[string]bool, len(m.tasks)) + for _, t := range m.tasks { + if t.enabled { + sel[t.name] = true + } + } + return SelectionResult{Selected: sel}, nil +} + +type selectableTask struct { + name string + enabled bool +} + +func (s selectableTask) Title() string { + if s.enabled { + return "[x] " + s.name + } + return "[ ] " + s.name +} +func (selectableTask) Description() string { return "" } +func (s selectableTask) FilterValue() string { return s.name } + +type selectionModel struct { + version string + notice string + modules []string + tasks []selectableTask + list list.Model + canceled bool + skipped bool +} + +func newSelectionModel(in SelectionInput) *selectionModel { + items := make([]list.Item, len(in.Tasks)) + selected := make([]selectableTask, len(in.Tasks)) + for i := range in.Tasks { + t := &in.Tasks[i] + selected[i] = selectableTask{name: t.Name, enabled: t.DefaultSelected} + items[i] = selected[i] + } + delegate := list.NewDefaultDelegate() + delegate.ShowDescription = false + delegate.SetSpacing(0) + + listHeight := len(in.Tasks) + 4 + l := list.New(items, delegate, 50, listHeight) + l.Title = "Select Prenup Tasks to Run" + l.SetShowStatusBar(false) + l.SetFilteringEnabled(false) + l.SetShowHelp(false) + l.DisableQuitKeybindings() + + return &selectionModel{ + version: in.Version, + notice: in.Notice, + modules: in.Modules, + tasks: selected, + list: l, + } +} + +func (m *selectionModel) Init() tea.Cmd { return nil } + +func (m *selectionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + chrome := 7 + len(m.modules) + if m.notice != "" { + chrome++ + } + h := msg.Height - chrome + if h < len(m.tasks)+4 { + h = len(m.tasks) + 4 + } + m.list.SetSize(msg.Width, h) + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + m.canceled = true + return m, tea.Quit + case "s": + m.skipped = true + return m, tea.Quit + case "enter": + return m, tea.Quit + case " ": + idx := m.list.Index() + if idx >= 0 && idx < len(m.tasks) { + m.tasks[idx].enabled = !m.tasks[idx].enabled + items := m.list.Items() + items[idx] = m.tasks[idx] + m.list.SetItems(items) + } + return m, nil + } + } + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + return m, cmd +} + +func (m *selectionModel) View() string { + header := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#00FF00")). + Render("Prenup Interactive Pre-Commit " + m.version) + warn := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFAA00")) + + var sb strings.Builder + sb.WriteString(header) + sb.WriteString("\n") + if m.notice != "" { + sb.WriteString(warn.Render(m.notice)) + sb.WriteString("\n") + } + sb.WriteString("\nChanged modules:\n") + for _, mod := range m.modules { + sb.WriteString(" - ") + sb.WriteString(mod) + sb.WriteString("\n") + } + sb.WriteString("\n") + sb.WriteString(m.list.View()) + sb.WriteString("\nup/down move, SPACE toggle, ENTER confirm, s skip (allow commit), q cancel (block commit)\n") + return sb.String() +} diff --git a/internal/ui/human/summary.go b/internal/ui/human/summary.go new file mode 100644 index 0000000..470f3c0 --- /dev/null +++ b/internal/ui/human/summary.go @@ -0,0 +1,115 @@ +package human + +import ( + "fmt" + "io" + "sync" + + "github.com/charmbracelet/lipgloss" + + "github.com/c2fo/prenup/internal/runner" +) + +// SummarySink collects events and prints a human-readable post-run summary to +// stdout after the alt-screen UI exits. Used in combination with the +// RunnerSink TUI to preserve visibility in clients that hide the alt-screen. +type SummarySink struct { + mu sync.Mutex + w io.Writer + version string + notice string + logs []string + taskOrder []string + seenTask map[string]bool + statuses map[string]runner.TaskStatus + durations map[string]int64 + notes map[string]string + summary runner.Event +} + +// NewSummarySink returns a SummarySink writing to w. +func NewSummarySink(w io.Writer) *SummarySink { + return &SummarySink{ + w: w, + seenTask: make(map[string]bool), + statuses: make(map[string]runner.TaskStatus), + durations: make(map[string]int64), + notes: make(map[string]string), + } +} + +// Emit records event data for later printing. +func (s *SummarySink) Emit(ev runner.Event) { + s.mu.Lock() + defer s.mu.Unlock() + switch ev.Kind { + case runner.EventRunStarted: + s.version = ev.Version + s.notice = ev.Message + case runner.EventLine: + s.logs = append(s.logs, ev.Text) + case runner.EventTaskStarted: + if !s.seenTask[ev.Task] { + s.seenTask[ev.Task] = true + s.taskOrder = append(s.taskOrder, ev.Task) + } + case runner.EventTaskCompleted: + if !s.seenTask[ev.Task] { + s.seenTask[ev.Task] = true + s.taskOrder = append(s.taskOrder, ev.Task) + } + s.statuses[ev.Task] = ev.Status + s.durations[ev.Task] += ev.DurationMs + if ev.Message != "" { + s.notes[ev.Task] = ev.Message + } + case runner.EventRunCompleted: + s.summary = ev + } +} + +// Close flushes the summary to the writer. +func (s *SummarySink) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + s.render() + return nil +} + +// writef writes to s.w, intentionally discarding errors (summary prints to +// stdout; a failing writer is rare and would fail every subsequent call). +func (s *SummarySink) writef(format string, a ...any) { + _, _ = fmt.Fprintf(s.w, format, a...) +} + +func (s *SummarySink) writeln(a ...any) { + _, _ = fmt.Fprintln(s.w, a...) +} + +func (s *SummarySink) render() { + s.writef("\nprenup %s\n", s.version) + if s.notice != "" { + s.writef("%s\n", s.notice) + } + s.writef("\nPrenup output:\n----------------\n") + for _, line := range s.logs { + s.writeln(line) + } + + s.writef("\nTask Summary:\n------------\n") + for _, name := range s.taskOrder { + symbol := lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00")).Render("v") + switch s.statuses[name] { + case runner.TaskStatusFailed: + symbol = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Render("x") + case runner.TaskStatusSkipped: + symbol = lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).Render("-") + } + extra := "" + if note := s.notes[name]; note != "" { + extra = " -- " + note + } + s.writef("%s %s (%dms)%s\n", symbol, name, s.durations[name], extra) + } + s.writef("\nSummary: %d succeeded, %d failed\n", s.summary.Succeeded, s.summary.Failed) +} diff --git a/internal/ui/jsonout/jsonout.go b/internal/ui/jsonout/jsonout.go new file mode 100644 index 0000000..ffd9e9b --- /dev/null +++ b/internal/ui/jsonout/jsonout.go @@ -0,0 +1,72 @@ +// Package jsonout is a runner.Sink that writes one JSON object per line. +// The schema is documented in prenup/docs/SCHEMA.md and is intended to be +// consumed by agents and scripting clients. +package jsonout + +import ( + "encoding/json" + "io" + "sync" + + "github.com/c2fo/prenup/internal/runner" + "github.com/c2fo/prenup/internal/ui" +) + +// Sink writes NDJSON events to w. +type Sink struct { + mu sync.Mutex + enc *json.Encoder +} + +// agentHint is the bootstrap NDJSON line emitted before any runner event. +// It carries the minimum self-describing context an agent needs to orient +// itself if it has no prior knowledge of prenup -- what the tool is, how +// to switch to/parse this stream, and what to do on failure. The schema +// version is bumped via ui.AgentHintSchema when the shape changes. +// +// Consumers that don't recognize the type field should skip the line and +// continue parsing as normal; the rest of the stream is unaffected. +type agentHint struct { + Type string `json:"type"` + Schema string `json:"schema"` + Tool string `json:"tool"` + Description string `json:"description"` + Homepage string `json:"homepage"` + HookContext string `json:"hook_context"` + BypassHint string `json:"bypass_hint"` + StreamFormat string `json:"stream_format"` + EventTypesNote string `json:"event_types_note"` +} + +// New constructs a Sink writing to w. It immediately writes a single +// `agent_hint` line so first-time consumers can identify the stream +// without prior knowledge of prenup. +func New(w io.Writer) *Sink { + enc := json.NewEncoder(w) + enc.SetEscapeHTML(false) + s := &Sink{enc: enc} + _ = enc.Encode(agentHint{ + Type: "agent_hint", + Schema: ui.AgentHintSchema, + Tool: ui.Tool, + Description: ui.Description, + Homepage: ui.HomepageURL, + HookContext: ui.HookContextNote, + BypassHint: ui.FailureBypassHint, + StreamFormat: "ndjson", + EventTypesNote: "Subsequent lines are runner events: run_started, " + + "task_started, line, task_completed, run_completed, notice. " + + "See prenup/docs/SCHEMA.md.", + }) + return s +} + +// Emit writes ev as a single line of JSON. +func (s *Sink) Emit(ev runner.Event) { + s.mu.Lock() + defer s.mu.Unlock() + _ = s.enc.Encode(ev) +} + +// Close is a no-op; the underlying writer is owned by the caller. +func (s *Sink) Close() error { return nil } diff --git a/internal/ui/jsonout/jsonout_test.go b/internal/ui/jsonout/jsonout_test.go new file mode 100644 index 0000000..1e73395 --- /dev/null +++ b/internal/ui/jsonout/jsonout_test.go @@ -0,0 +1,126 @@ +package jsonout + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + "github.com/c2fo/prenup/internal/runner" +) + +// JSONOutTestSuite groups behavioral tests for the NDJSON sink: the +// agent_hint bootstrap line, the per-event line shapes, and the run-level +// failure index. Each method gets a fresh buffer + sink via SetupTest. +type JSONOutTestSuite struct { + suite.Suite + buf *bytes.Buffer + sink *Sink + now time.Time +} + +func (s *JSONOutTestSuite) SetupTest() { + s.buf = &bytes.Buffer{} + s.sink = New(s.buf) + s.now = time.Now() +} + +// decodeLines splits the stream on newlines and JSON-decodes each +// non-empty line into a generic map. Returns the decoded objects so +// callers can assert by index. +func (s *JSONOutTestSuite) decodeLines() []map[string]any { + raw := strings.Split(strings.TrimRight(s.buf.String(), "\n"), "\n") + out := make([]map[string]any, 0, len(raw)) + for _, line := range raw { + if line == "" { + continue + } + var m map[string]any + s.Require().NoError(json.Unmarshal([]byte(line), &m), "line must be valid JSON: %q", line) + out = append(out, m) + } + return out +} + +func (s *JSONOutTestSuite) TestEmitWritesOneLinePerEvent() { + s.sink.Emit(runner.Event{ + Kind: runner.EventRunStarted, Time: s.now, Version: "v2.0.0", + Tasks: []string{"Run tests"}, RepoRoot: "/tmp/repo", + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventTaskStarted, Time: s.now, Task: "Run tests", Module: "pkg/foo", + Command: "go test ./...", WorkingDir: "/tmp/pkg/foo", + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventLine, Time: s.now, Task: "Run tests", Module: "pkg/foo", + Stream: runner.StreamStdout, Text: "ok", + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventTaskCompleted, Time: s.now, Task: "Run tests", + Status: runner.TaskStatusFailed, DurationMs: 12, + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventRunCompleted, Time: s.now, + Succeeded: 0, Failed: 1, ExitCode: 1, + FailedTasks: []string{"Run tests"}, + }) + + lines := s.decodeLines() + // 1 agent_hint bootstrap + 5 runner events. + s.Require().Len(lines, 6) + + hint := lines[0] + s.Equal("agent_hint", hint["type"], "first line must be the self-describing bootstrap") + s.Equal("prenup", hint["tool"]) + s.NotEmpty(hint["description"]) + s.NotEmpty(hint["homepage"]) + s.NotEmpty(hint["schema"]) + + first := lines[1] + s.Equal("run_started", first["type"]) + // run_started must carry repo_root so a JSON consumer can anchor + // every subsequent task's working_dir without inferring it from + // substring matches. + s.Equal("/tmp/repo", first["repo_root"]) + + // task_started must carry the resolved command and working_dir so a + // JSON-only consumer can reproduce the invocation without parsing + // .prenup.yaml or knowing prenup's template-expansion rules. + started := lines[2] + s.Equal("task_started", started["type"]) + s.Equal("go test ./...", started["command"]) + s.Equal("/tmp/pkg/foo", started["working_dir"]) + + line := lines[3] + s.Equal("line", line["type"]) + s.Equal("stdout", line["stream"]) + s.Equal("ok", line["text"]) + + // run_completed must carry the failed_tasks list so a JSON consumer + // has an O(1) failure index without rescanning task_completed events. + completed := lines[5] + s.Equal("run_completed", completed["type"]) + failedAny, ok := completed["failed_tasks"].([]any) + s.Require().True(ok, "failed_tasks must be present and an array") + s.Require().Len(failedAny, 1) + s.Equal("Run tests", failedAny[0]) +} + +// TestAgentHintIsFirstLineEvenWithNoEvents asserts the bootstrap line +// is written eagerly at construction. A consumer that opens the stream +// and immediately blocks on read sees the orienting context before any +// runner activity. +func (s *JSONOutTestSuite) TestAgentHintIsFirstLineEvenWithNoEvents() { + s.Require().NotEmpty(s.buf.Bytes(), "agent_hint must be emitted on construction") + first := strings.SplitN(s.buf.String(), "\n", 2)[0] + var obj map[string]any + s.Require().NoError(json.Unmarshal([]byte(first), &obj)) + s.Equal("agent_hint", obj["type"]) +} + +func TestJSONOutSuite(t *testing.T) { + suite.Run(t, new(JSONOutTestSuite)) +} diff --git a/internal/ui/markdown/markdown.go b/internal/ui/markdown/markdown.go new file mode 100644 index 0000000..d7c7161 --- /dev/null +++ b/internal/ui/markdown/markdown.go @@ -0,0 +1,228 @@ +// Package markdown is a runner.Sink that streams a plain-text progress feed +// during execution and emits a final structured markdown digest at the end. +// It is selected automatically when stdout is not a TTY. +package markdown + +import ( + "fmt" + "io" + "strings" + "sync" + + "github.com/c2fo/prenup/internal/runner" + "github.com/c2fo/prenup/internal/ui" +) + +// Sink renders markdown output. +type Sink struct { + mu sync.Mutex + w io.Writer + + version string + notice string + modules []string + selected []string + taskLogs map[string][]string // task -> accumulated lines (truncated tail kept) + taskStatus map[string]runner.TaskStatus + taskTime map[string]int64 + taskMessage map[string]string + taskOrder []string + succeeded int + failed int + exitCode int + failedTasks []string +} + +// New constructs a markdown Sink writing to w. +func New(w io.Writer) *Sink { + return &Sink{ + w: w, + taskLogs: make(map[string][]string), + taskStatus: make(map[string]runner.TaskStatus), + taskTime: make(map[string]int64), + taskMessage: make(map[string]string), + } +} + +const maxFailureTail = 50 + +// writef writes a formatted line to s.w, ignoring transient write errors on +// stdout/pipes. A closed/failing writer would also fail every subsequent call, +// so surfacing each individual error adds no value. +func (s *Sink) writef(format string, a ...any) { + _, _ = fmt.Fprintf(s.w, format, a...) +} + +// writeln writes a line (or newline-only string) to s.w, ignoring errors. +func (s *Sink) writeln(a ...any) { + _, _ = fmt.Fprintln(s.w, a...) +} + +// Emit handles an event by streaming a short human line now and accumulating +// state for the final digest. +func (s *Sink) Emit(ev runner.Event) { + s.mu.Lock() + defer s.mu.Unlock() + + switch ev.Kind { + case runner.EventRunStarted: + s.handleRunStarted(ev) + case runner.EventTaskStarted: + s.handleTaskStarted(ev) + case runner.EventLine: + s.handleLine(ev) + case runner.EventTaskCompleted: + s.handleTaskCompleted(ev) + case runner.EventRunCompleted: + s.succeeded = ev.Succeeded + s.failed = ev.Failed + s.exitCode = ev.ExitCode + s.failedTasks = ev.FailedTasks + s.renderDigest() + case runner.EventNotice: + s.writef("Notice: %s\n", ev.Message) + } +} + +func (s *Sink) handleRunStarted(ev runner.Event) { + s.version = ev.Version + s.notice = ev.Message + s.modules = ev.Modules + s.selected = ev.Tasks + + // Self-describing preamble so an agent (or operator) that has never + // heard of prenup can identify the tool and understand what just + // happened from a single git-commit transcript. Humans see the TUI + // instead of this and so do not need it. + s.writef("[%s] %s\n", ui.Tool, ui.Description) + s.writef("[%s] Docs: %s\n", ui.Tool, ui.HomepageURL) + s.writef("[%s] %s\n", ui.Tool, ui.JSONHint) + s.writeln() + + s.writef("Prenup version: %s\n", ui.VersionLabel(s.version)) + if s.notice != "" { + s.writef("%s\n", s.notice) + } + if len(s.modules) > 0 { + s.writef("Modules: %s\n", strings.Join(s.modules, ", ")) + } + s.writef("Tasks: %s\n\n", strings.Join(s.selected, ", ")) +} + +func (s *Sink) handleTaskStarted(ev runner.Event) { + if _, ok := s.taskLogs[ev.Task]; !ok { + s.taskOrder = append(s.taskOrder, ev.Task) + s.taskLogs[ev.Task] = nil + } + if ev.Module != "" { + s.writef("> %s (%s)\n", ev.Task, ev.Module) + } else { + s.writef("> %s\n", ev.Task) + } + // Echo the resolved command so the live stream documents exactly what + // was about to run, and so the failure-tail digest carries enough to + // reproduce the invocation without reading .prenup.yaml. Routed + // through appendLog so it lands in the output_tail on failure. + if ev.Command != "" { + echo := "[prenup] $ " + ev.Command + if ev.WorkingDir != "" { + echo += " (cwd: " + ev.WorkingDir + ")" + } + s.writeln(echo) + s.appendLog(ev.Task, echo) + } +} + +// handleLine prefixes [module] in parallel runs so the digest tail is +// unambiguous about which module produced each output line. +func (s *Sink) handleLine(ev runner.Event) { + line := ev.Text + if ev.Module != "" && ev.Module != "." { + line = "[" + ev.Module + "] " + line + } + s.writeln(line) + s.appendLog(ev.Task, line) +} + +func (s *Sink) handleTaskCompleted(ev runner.Event) { + s.taskStatus[ev.Task] = ev.Status + s.taskTime[ev.Task] = ev.DurationMs + if ev.Message != "" { + s.taskMessage[ev.Task] = ev.Message + } + symbol := "OK" + switch ev.Status { + case runner.TaskStatusFailed: + symbol = "FAIL" + case runner.TaskStatusSkipped: + symbol = "SKIP" + } + s.writef("[%s] %s (%dms)\n\n", symbol, ev.Task, ev.DurationMs) +} + +// Close flushes any buffered state. The digest is emitted on EventRunCompleted, +// so Close is a no-op unless that event was missed. +func (s *Sink) Close() error { return nil } + +func (s *Sink) appendLog(task, line string) { + if task == "" { + return + } + logs := s.taskLogs[task] + logs = append(logs, line) + if len(logs) > maxFailureTail { + logs = logs[len(logs)-maxFailureTail:] + } + s.taskLogs[task] = logs +} + +func (s *Sink) renderDigest() { + s.writeln("---") + s.writef("## Prenup %s\n\n", ui.VersionLabel(s.version)) + if s.notice != "" { + s.writef("> %s\n\n", s.notice) + } + s.writef("**Summary:** %d succeeded, %d failed (exit %d)\n", s.succeeded, s.failed, s.exitCode) + // Surface failed task names directly under the summary so an agent + // scanning a long digest can jump straight to the failures without + // reading every per-task section. + if len(s.failedTasks) > 0 { + s.writef("**Failed tasks:** %s\n", strings.Join(s.failedTasks, ", ")) + } + s.writeln() + + for _, name := range s.taskOrder { + status := s.taskStatus[name] + s.writef("### Task: %s\n", name) + s.writef("- Status: `%s`\n", status) + s.writef("- Duration: %dms\n", s.taskTime[name]) + if msg := s.taskMessage[name]; msg != "" { + s.writef("- Note: %s\n", msg) + } + if status == runner.TaskStatusFailed { + tail := s.taskLogs[name] + if len(tail) > 0 { + s.writeln() + s.writeln("
Output tail") + s.writeln() + s.writeln("```") + for _, line := range tail { + s.writeln(line) + } + s.writeln("```") + s.writeln("
") + } + } + s.writeln() + } + + if s.failed > 0 { + s.writeln("### Next steps") + // Lead with the hook-context line so consumers understand the + // commit was blocked by prenup, not by `git` itself. + s.writef("- %s\n", ui.HookContextNote) + s.writeln("- Reproduce the failing task locally, then re-run `git commit`.") + s.writeln("- Use `prenup run --task \"\"` to rerun a specific task without committing.") + s.writef("- %s\n", ui.FailureBypassHint) + } +} diff --git a/internal/ui/markdown/markdown_test.go b/internal/ui/markdown/markdown_test.go new file mode 100644 index 0000000..8084ef4 --- /dev/null +++ b/internal/ui/markdown/markdown_test.go @@ -0,0 +1,207 @@ +package markdown + +import ( + "bytes" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + "github.com/c2fo/prenup/internal/runner" +) + +// MarkdownSinkTestSuite groups behavioral tests for the markdown sink: the +// agent-orienting preamble, the live event stream, and the structured digest +// emitted on EventRunCompleted. SetupTest gives every method a fresh +// buffer + sink so they can run in any order. +type MarkdownSinkTestSuite struct { + suite.Suite + buf *bytes.Buffer + sink *Sink + now time.Time +} + +func (s *MarkdownSinkTestSuite) SetupTest() { + s.buf = &bytes.Buffer{} + s.sink = New(s.buf) + s.now = time.Now() +} + +// emitSample drives a representative one-task run through the sink. +// failing controls whether the task ends in TaskStatusFailed (with a +// FAIL stderr line and a populated FailedTasks list on run_completed). +func (s *MarkdownSinkTestSuite) emitSample(failing bool) { + s.sink.Emit(runner.Event{ + Kind: runner.EventRunStarted, Time: s.now, Version: "v2.0.0", + Modules: []string{"pkg/foo"}, Tasks: []string{"Run tests"}, + RepoRoot: "/tmp/repo", + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventTaskStarted, Time: s.now, Task: "Run tests", Module: "pkg/foo", + Command: "go test ./...", WorkingDir: "/tmp/pkg/foo", + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventLine, Time: s.now, Task: "Run tests", Module: "pkg/foo", + Stream: runner.StreamStdout, Text: "ok pkg/foo", + }) + + status := runner.TaskStatusDone + exit := 0 + failed := 0 + var failedTasks []string + if failing { + s.sink.Emit(runner.Event{ + Kind: runner.EventLine, Time: s.now, Task: "Run tests", Module: "pkg/foo", + Stream: runner.StreamStderr, Text: "FAIL", + }) + status = runner.TaskStatusFailed + exit = 1 + failed = 1 + failedTasks = []string{"Run tests"} + } + s.sink.Emit(runner.Event{ + Kind: runner.EventTaskCompleted, Time: s.now, Task: "Run tests", + Status: status, DurationMs: 42, + }) + s.sink.Emit(runner.Event{ + Kind: runner.EventRunCompleted, Time: s.now, + Succeeded: 1 - failed, Failed: failed, ExitCode: exit, + FailedTasks: failedTasks, + }) +} + +// emitVersion renders a tiny stream with a custom version token so the +// version-label cases can exercise the labeling helper without rebuilding +// the whole fixture. +func (s *MarkdownSinkTestSuite) emitVersion(version string) { + s.sink.Emit(runner.Event{ + Kind: runner.EventRunStarted, Time: s.now, Version: version, + Modules: []string{"."}, Tasks: []string{"Echo"}, + }) + s.sink.Emit(runner.Event{Kind: runner.EventTaskStarted, Time: s.now, Task: "Echo"}) + s.sink.Emit(runner.Event{ + Kind: runner.EventTaskCompleted, Time: s.now, Task: "Echo", + Status: runner.TaskStatusDone, + }) + s.sink.Emit(runner.Event{Kind: runner.EventRunCompleted, Time: s.now, Succeeded: 1}) +} + +func (s *MarkdownSinkTestSuite) TestSuccessDigestIsQuiet() { + s.emitSample(false) + out := s.buf.String() + s.Contains(out, "Prenup version: v2.0.0") + s.Contains(out, "## Prenup v2.0.0") + s.Contains(out, "1 succeeded, 0 failed") + s.Contains(out, "### Task: Run tests") + s.Contains(out, "Status: `done`") + s.NotContains(out, "Next steps") + // Successful runs deliberately omit the "Failed tasks:" line; its + // absence is what tells a scanning agent that nothing went wrong. + s.NotContains(out, "Failed tasks:") +} + +func (s *MarkdownSinkTestSuite) TestPreambleIdentifiesTool() { + s.emitSample(false) + out := s.buf.String() + // Cold-start agent must be able to identify the tool, find docs, + // and discover the JSON output mode from the very first lines. + s.Contains(out, "[prenup]", "preamble must be tagged so it stands out from streamed task output") + s.Contains(out, "Git pre-commit hook", "preamble must explain what prenup is") + s.Contains(out, "Docs:", "preamble must point at human-facing docs") + s.Contains(out, "--output json", "preamble must advertise the structured output mode") +} + +// TestVersionLabel runs both the dev-build and release-version paths +// through the same fixture; the labeling rule is shared between the +// preamble and the digest header, so both must agree. +func (s *MarkdownSinkTestSuite) TestVersionLabel() { + cases := []struct { + name string + version string + wantPreamble string + wantHeader string + wantQualifier bool // true if the "(development build...)" parenthetical must appear + }{ + { + name: "dev build is qualified", + version: "dev", + wantPreamble: "Prenup version: dev (development build", + wantHeader: "## Prenup dev (development build", + wantQualifier: true, + }, + { + name: "release tag renders cleanly", + version: "v2.3.1", + wantPreamble: "Prenup version: v2.3.1\n", + wantHeader: "## Prenup v2.3.1\n", + wantQualifier: false, + }, + } + for _, tc := range cases { + s.Run(tc.name, func() { + // SetupTest does not run for child suite.Run cases, so reset by + // hand to keep the cases independent. + s.buf = &bytes.Buffer{} + s.sink = New(s.buf) + s.emitVersion(tc.version) + out := s.buf.String() + s.Contains(out, tc.wantPreamble) + s.Contains(out, tc.wantHeader) + if tc.wantQualifier { + s.Contains(out, "development build") + } else { + s.NotContains(out, "development build") + } + }) + } +} + +func (s *MarkdownSinkTestSuite) TestFailureDigestIncludesTailAndNextSteps() { + s.emitSample(true) + out := s.buf.String() + s.Contains(out, "Status: `failed`") + s.Contains(out, "FAIL") + s.Contains(out, "Next steps") + s.Contains(out, "
Output tail") + // Failure footer must clarify hook-vs-git attribution and bypass. + s.Contains(out, "not by `git`", "footer must disambiguate prenup vs git") + s.Contains(out, "--no-verify", "footer must document the bypass") + s.Contains(out, ".prenup.yaml", "footer must point at the config") +} + +// TestEchoesResolvedCommand keeps the agent-reproducibility guarantee +// honest: an agent reading the failure tail must see the literal shell +// command (and resolved cwd) prenup ran, not just the task name. +func (s *MarkdownSinkTestSuite) TestEchoesResolvedCommand() { + s.emitSample(true) + out := s.buf.String() + s.Contains(out, "[prenup] $ go test ./...", "command must be echoed in the live stream") + s.Contains(out, "(cwd: /tmp/pkg/foo)", "resolved working directory must be shown") + + tailIdx := strings.Index(out, "
Output tail") + s.Require().GreaterOrEqual(tailIdx, 0, "output tail block must exist") + tail := out[tailIdx:] + s.Contains(tail, "go test ./...", "command must be in the failure tail for reproducibility") +} + +// TestFailedTasksSummary keeps the at-a-glance failure index honest: +// the summary line must be followed by a "Failed tasks: ..." line +// listing the failing task names so an agent reading a long digest can +// jump directly to them. +func (s *MarkdownSinkTestSuite) TestFailedTasksSummary() { + s.emitSample(true) + out := s.buf.String() + s.Require().Contains(out, "**Failed tasks:** Run tests") + + summaryIdx := strings.Index(out, "**Summary:**") + failedIdx := strings.Index(out, "**Failed tasks:**") + s.Require().GreaterOrEqual(summaryIdx, 0) + s.Require().Greater(failedIdx, summaryIdx) + between := out[summaryIdx:failedIdx] + s.NotContains(between, "### Task:", "Failed tasks: must appear before any per-task section") +} + +func TestMarkdownSinkSuite(t *testing.T) { + suite.Run(t, new(MarkdownSinkTestSuite)) +} diff --git a/internal/ui/output.go b/internal/ui/output.go new file mode 100644 index 0000000..e6e75df --- /dev/null +++ b/internal/ui/output.go @@ -0,0 +1,37 @@ +// Package ui wires the runner's event stream into the user's chosen renderer +// (human TUI, markdown digest, or NDJSON stream). +package ui + +import ( + "os" + + "github.com/mattn/go-isatty" + + "github.com/c2fo/prenup/internal/config" +) + +// Resolve collapses "auto" to a concrete mode based on environment signals. +// Rules: +// - explicit override wins +// - NO_COLOR or TERM=dumb -> markdown +// - stdout not a TTY -> markdown +// - otherwise -> human +// +// "json" is only ever returned when explicitly requested. +func Resolve(requested config.OutputMode) config.OutputMode { + switch requested { + case config.OutputHuman, config.OutputMarkdown, config.OutputJSON: + return requested + } + if os.Getenv("NO_COLOR") != "" { + return config.OutputMarkdown + } + if term := os.Getenv("TERM"); term == "dumb" { + return config.OutputMarkdown + } + fd := os.Stdout.Fd() + if isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) { + return config.OutputHuman + } + return config.OutputMarkdown +} diff --git a/internal/versioncheck/versioncheck.go b/internal/versioncheck/versioncheck.go new file mode 100644 index 0000000..fe5e4f4 --- /dev/null +++ b/internal/versioncheck/versioncheck.go @@ -0,0 +1,160 @@ +// Package versioncheck compares the running prenup binary version to the latest GitHub release. +package versioncheck + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "golang.org/x/mod/semver" +) + +const ( + repoOwner = "c2fo" + repoName = "prenup" + tagPrefix = "v" + apiTimeout = 10 * time.Second + releasesURL = "https://api.github.com/repos/%s/%s/releases" +) + +// Release represents a GitHub release. +type Release struct { + TagName string `json:"tag_name"` + HTMLURL string `json:"html_url"` +} + +// CheckResult contains the result of a version check. +type CheckResult struct { + CurrentVersion string + LatestVersion string + LatestURL string + IsOutdated bool + IsAhead bool // Running unreleased commits ahead of latest release + Error error +} + +// Check compares the current version against the latest GitHub release for prenup. +// If githubToken is non-empty, it is sent as a Bearer token (needed for private repos). +func Check(ctx context.Context, currentVersion, githubToken string) CheckResult { + apiURL := fmt.Sprintf(releasesURL, repoOwner, repoName) + return checkAgainstReleasesAPI(ctx, currentVersion, githubToken, apiURL) +} + +func checkAgainstReleasesAPI(ctx context.Context, currentVersion, githubToken, apiURL string) CheckResult { + result := CheckResult{ + CurrentVersion: currentVersion, + } + + if currentVersion == "dev" || currentVersion == "" { + result.Error = errors.New("running development build (version not set)") + return result + } + + latest, url, err := fetchLatestPrenupRelease(ctx, githubToken, apiURL) + if err != nil { + result.Error = fmt.Errorf("failed to check for updates: %w", err) + return result + } + + result.LatestVersion = latest + result.LatestURL = url + + current := normalizeVersion(currentVersion) + latestNorm := normalizeVersion(latest) + currentBase := extractBaseVersion(current) + + if !semver.IsValid(currentBase) || !semver.IsValid(latestNorm) { + result.Error = fmt.Errorf("invalid version format: current=%q, latest=%q", currentBase, latestNorm) + return result + } + + cmp := semver.Compare(currentBase, latestNorm) + switch { + case cmp < 0: + result.IsOutdated = true + case cmp == 0 && current != currentBase: + result.IsAhead = true + case cmp > 0: + result.IsAhead = true + } + + return result +} + +func fetchLatestPrenupRelease(ctx context.Context, githubToken, apiURL string) (version, url string, err error) { + ctx, cancel := context.WithTimeout(ctx, apiTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, http.NoBody) + if err != nil { + return "", "", err + } + req.Header.Set("Accept", "application/vnd.github.v3+json") + if githubToken != "" { + req.Header.Set("Authorization", "Bearer "+githubToken) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode == http.StatusForbidden && resp.Header.Get("X-RateLimit-Remaining") == "0" { + return "", "", fmt.Errorf("GitHub API rate limit exceeded (resets at %s)", + resp.Header.Get("X-RateLimit-Reset")) + } + if resp.StatusCode != http.StatusOK { + return "", "", fmt.Errorf("GitHub API returned status %d", resp.StatusCode) + } + + var releases []Release + if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { + return "", "", err + } + + var latestVer string + var latestURL string + for _, r := range releases { + if !strings.HasPrefix(r.TagName, tagPrefix) { + continue + } + ver := strings.TrimPrefix(r.TagName, tagPrefix) + normalized := normalizeVersion(ver) + if !semver.IsValid(normalized) { + continue + } + if latestVer == "" || semver.Compare(normalized, normalizeVersion(latestVer)) > 0 { + latestVer = ver + latestURL = r.HTMLURL + } + } + + if latestVer == "" { + return "", "", errors.New("no prenup releases found") + } + + return latestVer, latestURL, nil +} + +func normalizeVersion(v string) string { + v = strings.TrimSpace(v) + if !strings.HasPrefix(v, "v") { + v = "v" + v + } + return v +} + +func extractBaseVersion(v string) string { + parts := strings.Split(v, "-") + if len(parts) >= 3 { + if len(parts[len(parts)-1]) > 1 && parts[len(parts)-1][0] == 'g' { + return parts[0] + } + } + return v +} diff --git a/internal/versioncheck/versioncheck_test.go b/internal/versioncheck/versioncheck_test.go new file mode 100644 index 0000000..469dc4a --- /dev/null +++ b/internal/versioncheck/versioncheck_test.go @@ -0,0 +1,295 @@ +package versioncheck + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExtractBaseVersion(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {name: "clean version", input: "v1.2.0", expected: "v1.2.0"}, + {name: "git describe with commits after tag", input: "v1.2.0-1-g71f9e6d", expected: "v1.2.0"}, + {name: "git describe with many commits", input: "v1.2.0-15-gabcdef1", expected: "v1.2.0"}, + {name: "actual prerelease (not git describe)", input: "v1.2.0-beta.1", expected: "v1.2.0-beta.1"}, + {name: "actual prerelease rc", input: "v1.2.0-rc1", expected: "v1.2.0-rc1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, extractBaseVersion(tt.input)) + }) + } +} + +func TestNormalizeVersion(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {name: "already has v prefix", input: "v1.2.3", expected: "v1.2.3"}, + {name: "missing v prefix", input: "1.2.3", expected: "v1.2.3"}, + {name: "with whitespace", input: " v1.2.3 ", expected: "v1.2.3"}, + {name: "whitespace without v", input: " 1.2.3 ", expected: "v1.2.3"}, + {name: "prerelease version", input: "v1.2.3-beta.1", expected: "v1.2.3-beta.1"}, + {name: "empty string", input: "", expected: "v"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, normalizeVersion(tt.input)) + }) + } +} + +func TestCheck_DevBuild(t *testing.T) { + tests := []struct { + name string + version string + }{ + {name: "dev version", version: "dev"}, + {name: "empty version", version: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Check(context.Background(), tt.version, "") + + assert.Equal(t, tt.version, result.CurrentVersion) + assert.Empty(t, result.LatestVersion) + assert.Empty(t, result.LatestURL) + assert.False(t, result.IsOutdated) + require.Error(t, result.Error) + assert.Contains(t, result.Error.Error(), "development build") + }) + } +} + +func TestCheck_WithMockServer(t *testing.T) { + tests := []struct { + name string + currentVersion string + releases []Release + statusCode int + wantOutdated bool + wantAhead bool + wantError bool + errorContains string + }{ + { + name: "current version is latest", + currentVersion: "v1.2.0", + releases: []Release{ + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + {TagName: "v1.1.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.1.0"}, + }, + statusCode: http.StatusOK, + }, + { + name: "current version is outdated", + currentVersion: "v1.1.0", + releases: []Release{ + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + {TagName: "v1.1.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.1.0"}, + }, + statusCode: http.StatusOK, + wantOutdated: true, + }, + { + name: "current version without v prefix is outdated", + currentVersion: "1.1.0", + releases: []Release{ + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + }, + statusCode: http.StatusOK, + wantOutdated: true, + }, + { + name: "current version is newer than latest", + currentVersion: "v1.3.0", + releases: []Release{ + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + }, + statusCode: http.StatusOK, + wantAhead: true, + }, + { + name: "git-describe version with commits after latest tag is ahead", + currentVersion: "v1.2.0-1-g71f9e6d", + releases: []Release{ + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + }, + statusCode: http.StatusOK, + wantAhead: true, + }, + { + name: "git-describe version IS outdated when newer release exists", + currentVersion: "v1.1.0-5-g1234567", + releases: []Release{ + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + }, + statusCode: http.StatusOK, + wantOutdated: true, + }, + { + name: "patch version comparison", + currentVersion: "v1.2.0", + releases: []Release{ + {TagName: "v1.2.1", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.1"}, + }, + statusCode: http.StatusOK, + wantOutdated: true, + }, + { + name: "API returns error status", + currentVersion: "v1.2.0", + statusCode: http.StatusInternalServerError, + wantError: true, + errorContains: "status 500", + }, + { + name: "no prenup releases found", + currentVersion: "v1.2.0", + releases: []Release{ + {TagName: "rowtater/v1.0.0", HTMLURL: "https://github.com/c2fo/rowtater/releases/tag/v1.0.0"}, + }, + statusCode: http.StatusOK, + wantError: true, + errorContains: "no prenup releases found", + }, + { + name: "empty releases list", + currentVersion: "v1.2.0", + releases: []Release{}, + statusCode: http.StatusOK, + wantError: true, + errorContains: "no prenup releases found", + }, + { + name: "skips other tools to find prenup", + currentVersion: "v1.1.0", + releases: []Release{ + {TagName: "releasegen/v1.0.0", HTMLURL: "https://github.com/c2fo/releasegen/releases/tag/v1.0.0"}, + {TagName: "v1.2.0", HTMLURL: "https://github.com/c2fo/prenup/releases/tag/v1.2.0"}, + }, + statusCode: http.StatusOK, + wantOutdated: true, + }, + { + name: "selects highest semver regardless of API order", + currentVersion: "v1.2.0", + releases: []Release{ + {TagName: "v1.1.0", HTMLURL: "https://example.com/a"}, + {TagName: "v1.3.0", HTMLURL: "https://example.com/b"}, + {TagName: "v1.2.0", HTMLURL: "https://example.com/c"}, + }, + statusCode: http.StatusOK, + wantOutdated: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "application/vnd.github.v3+json", r.Header.Get("Accept")) + w.WriteHeader(tt.statusCode) + if tt.releases != nil { + _ = json.NewEncoder(w).Encode(tt.releases) + } + })) + defer server.Close() + + result := checkAgainstReleasesAPI(context.Background(), tt.currentVersion, "", server.URL) + + assert.Equal(t, tt.currentVersion, result.CurrentVersion) + assert.Equal(t, tt.wantOutdated, result.IsOutdated, "IsOutdated mismatch") + assert.Equal(t, tt.wantAhead, result.IsAhead, "IsAhead mismatch") + + if tt.wantError { + require.Error(t, result.Error) + if tt.errorContains != "" { + assert.Contains(t, result.Error.Error(), tt.errorContains) + } + } else { + require.NoError(t, result.Error) + assert.NotEmpty(t, result.LatestVersion) + assert.NotEmpty(t, result.LatestURL) + } + }) + } +} + +func TestCheck_ContextCancellation(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(100 * time.Millisecond) + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode([]Release{{TagName: "v1.0.0"}}) + })) + defer server.Close() + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + result := checkAgainstReleasesAPI(ctx, "v1.0.0", "", server.URL) + + require.Error(t, result.Error) + assert.Contains(t, result.Error.Error(), "context canceled") +} + +func TestCheck_WithGitHubToken(t *testing.T) { + var receivedToken string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedToken = r.Header.Get("Authorization") + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode([]Release{ + {TagName: "v1.0.0", HTMLURL: "https://example.com"}, + }) + })) + defer server.Close() + + result := checkAgainstReleasesAPI(context.Background(), "v1.0.0", "my-secret-token", server.URL) + + require.NoError(t, result.Error) + assert.Equal(t, "Bearer my-secret-token", receivedToken) +} + +func TestCheck_RateLimited(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("X-RateLimit-Remaining", "0") + w.Header().Set("X-RateLimit-Reset", "1700000000") + w.WriteHeader(http.StatusForbidden) + })) + defer server.Close() + + result := checkAgainstReleasesAPI(context.Background(), "v1.0.0", "", server.URL) + + require.Error(t, result.Error) + assert.Contains(t, result.Error.Error(), "rate limit exceeded") + assert.Contains(t, result.Error.Error(), "1700000000") +} + +func TestCheck_InvalidCurrentVersion(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode([]Release{ + {TagName: "v1.0.0", HTMLURL: "https://example.com"}, + }) + })) + defer server.Close() + + result := checkAgainstReleasesAPI(context.Background(), "not-a-version", "", server.URL) + + require.Error(t, result.Error) + assert.Contains(t, result.Error.Error(), "invalid version format") +} diff --git a/prenup.example.yaml b/prenup.example.yaml new file mode 100644 index 0000000..8759c62 --- /dev/null +++ b/prenup.example.yaml @@ -0,0 +1,61 @@ +# Prenup pre-commit hook configuration. +# +# Place this file at the root of your git repository as `.prenup.yaml` (or +# `.prenup.yml`). Run `prenup install` once per repo to wire up the hook. +# +# See https://github.com/c2fo/prenup/blob/main/docs/SCHEMA.md for a full +# reference and https://github.com/c2fo/prenup/blob/main/assets/prenup.schema.json +# for an editor-friendly JSON schema. + +version: 2 + +# Files whose presence marks a "module". Defaults to [go.mod]. Configurable so +# polyglot repos can opt in to other languages. +module_markers: + - go.mod + +# Files matching any pattern here are ignored for change detection. Syntax is +# doublestar, so `**` crosses directory boundaries. +exclude: + - ".github/**" + - "**/*.yaml" + - "**/*.yml" + +# Stash unstaged changes around the run so tasks see exactly what will be +# committed. Override per-task below if needed. +clean_worktree: true + +# 0 means NumCPU. Applies to per-module fan-out within a single task. +max_parallelism: 0 + +# auto picks human for TTYs, markdown when piped. Override with --output. +output: auto + +tasks: + - name: "Run tests" + default_selected: true + command: "go test ./..." + per_module: true + paths: + - "**/*.go" + + - name: "Run golangci-lint" + default_selected: true + command: "golangci-lint run --max-same-issues 0 ./..." + per_module: true + paths: + - "**/*.go" + + - name: "go vet" + default_selected: true + command: "go vet ./..." + per_module: true + paths: + - "**/*.go" + + - name: "gofmt check" + default_selected: true + command: "test -z \"$(gofmt -l .)\"" + per_module: true + paths: + - "**/*.go" diff --git a/prenup.png b/prenup.png new file mode 100644 index 0000000000000000000000000000000000000000..901049fca95de8e9fec2d8372372b8a547787414 GIT binary patch literal 273730 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 dK^D<8bMAY1TL@ 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 Vi_Kio<2e8T002ovPDHLkV1gmBJWBuo literal 0 HcmV?d00001 From 1d185bebb9c6b17fdc5cf04c4595827b94a9915b Mon Sep 17 00:00:00 2001 From: John Judd Date: Sat, 4 Jul 2026 15:32:24 -0500 Subject: [PATCH 2/7] docs: design for open-sourcing prenup as github.com/c2fo/prenup --- .../2026-07-03-open-source-prenup-design.md | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-03-open-source-prenup-design.md diff --git a/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md b/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md new file mode 100644 index 0000000..cccd848 --- /dev/null +++ b/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md @@ -0,0 +1,168 @@ +# Open-Sourcing `prenup` as `github.com/c2fo/prenup` + +**Status:** Approved +**Date:** 2026-07-03 +**Owner:** @jjudd + +## Goal + +Extract `prenup` from the internal `ep-tools` monorepo into a standalone open-source +repository at `github.com/c2fo/prenup`, using `github.com/c2fo/releasegen` as the +scaffolding template. Ship as `v0.1.0`. + +## Source, Target, Reference + +- **Source (internal):** `/Users/john.judd/gitprojects/ep-tools/prenup` + (module `github.com/c2fo/ep-tools/prenup/v2`) +- **Target (fresh repo, remote pre-configured):** `/Users/john.judd/gitprojects/prenup` + (module will be `github.com/c2fo/prenup`) +- **Reference (scaffolding template):** `/Users/john.judd/gitprojects/releasegen` + +## Design + +### 1. Repo bootstrap — fresh history + +Copy the working tree from `ep-tools/prenup` into `gitprojects/prenup`. Do not +carry any internal git history; the first commit on `main` is the open-source +baseline. Overwrite the stub `README.md`. Exclude `.git`, IDE files, and any +build artifacts. + +### 2. Module + import-path rename + +Rename `github.com/c2fo/ep-tools/prenup/v2` → `github.com/c2fo/prenup` (drop the +`/v2` major-version suffix since we're resetting to `v0.1.0`). Rewrite every +import in `.go` sources, update `go.mod`, update `.mockery.yaml`, regenerate +mocks, run `go mod tidy`, and confirm `go build ./...` and `go test ./...` still +pass. + +### 3. Scrub internal references + +Based on the audit report, apply these changes: + +**Code:** +- `internal/versioncheck/versioncheck.go`: `repoOwner="c2fo"`, `repoName="prenup"`, + `tagPrefix="v"` (was `C2FO`/`ep-tools`/`prenup/`). +- `internal/cli/version.go`: remove `strings.TrimPrefix(..., "prenup/")` calls + (module version strings will now be plain `vX.Y.Z`). +- `internal/ui/agent.go`: `HomepageURL = "https://github.com/c2fo/prenup"`. +- `internal/config/migrate.go`: update the docs-URL header comment. +- `internal/cli/run.go`: update the install-command hint string + (`go install github.com/c2fo/prenup/cmd/prenup@latest`). +- `internal/versioncheck/versioncheck_test.go`: update fixture URLs to + `c2fo/prenup`; remove or repoint the `rowtater` / `releasegen` sibling-tag + fixtures. + +**Config/schemas/assets:** +- `assets/prenup.schema.json` + `internal/config/schema.json`: update `$id` to + `https://raw.githubusercontent.com/c2fo/prenup/main/assets/prenup.schema.json`. +- `.mockery.yaml`: update package path. +- `prenup.example.yaml`: replace the `.github/workflows/scripts/changelog.sh` and + `make docs` tasks with generic standalone examples (e.g. `go test`, `go vet`, + a `releasegen validate` hook — same pattern as sibling projects). + +**Docs:** +- `README.md`: rewrite for a public audience — what prenup is (git-hook manager), + install via `go install`, quickstart, config reference. Fix the + `../LICENSE` reference to `LICENSE.md` in-repo. +- `docs/PRD.md`, `docs/V2_PLAN.md`, `docs/SCHEMA.md`, `docs/FUTURE.md`: scrub + monorepo-relative paths (`prenup/README.md` → `README.md`), update module + paths and URLs. Consider whether `V2_PLAN.md` is worth keeping in the public + repo (historical rewrite plan); if not, drop it. +- `cmd/prenup/main.go` comment: fix monorepo-relative doc paths. +- `internal/config/schema_test.go`: fix the byte-identity error message paths. + +### 4. Open-source scaffolding (from releasegen) + +Add these files, adapted for prenup: + +| File | Adaptation | +| --- | --- | +| `LICENSE.md` | Copy verbatim (MIT, C2FO Inc.) | +| `CODE_OF_CONDUCT.md` | Copy verbatim | +| `.golangci.yml` | Copy; change `prefix` / `local-prefixes` to `github.com/c2fo/prenup` | +| `.testcoverage.yml` | Copy; leave thresholds — tune after first CI run | +| `.github/ISSUE_TEMPLATE/{bug_report,feature_request,custom}.md` | Copy; retarget prenup | +| `.github/workflows/ci.yml` | Copy verbatim | +| `.github/workflows/golangci-lint.yml` | Copy verbatim | +| `.github/workflows/codeql.yml` | Copy verbatim | +| `.github/workflows/go-test-coverage.yml` | Copy verbatim | +| `.github/workflows/validate-changelog.yml` | Copy verbatim (already uses `releasegen validate`) | + +Note: **not** copying releasegen's `deployments/Dockerfile` — prenup does not +ship a container. + +### 5. Release workflow — vfs-style (tag-only) + +Copy `.github/workflows/releasegen.yml` from **vfs**, not releasegen. Rationale: +releasegen's own workflow builds itself and pushes a Docker image (self-release +special case); vfs's workflow just runs the pre-built +`ghcr.io/c2fo/releasegen:v1` image to cut a tag and GitHub release from the +changelog — which is exactly what prenup needs. + +Also add a `.releasegen.yaml` at the repo root (adapted from releasegen's), so +the release and validate workflows share one source of truth for change-type +config. + +**Distribution:** users install via `go install github.com/c2fo/prenup/cmd/prenup@latest`. +No Docker image, no GoReleaser binaries. + +### 6. CHANGELOG reset + +Replace the entire `CHANGELOG.md` with: + +```markdown +# Changelog + +All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2026-07-04 + +### Added +- Initial open-source release of `prenup`, a git pre-commit hook manager. +- `run`, `plan`, `install`/`uninstall`, `init`, `migrate`, `config`, `version` + commands. +- v2 YAML configuration schema with published JSON Schema for editor support. +- Change detection with module-marker discovery for monorepo-friendly parallel + execution. +- Human (Bubble Tea TUI), NDJSON, and markdown output modes. +- Update check against GitHub Releases. +``` + +The internal `v2.x` history is dropped entirely. + +### 7. Dogfooding config + +Copy the releasegen `.prenup.yaml` template, adapted for a Go project of +prenup's shape (lint, test, changelog-validate). This makes prenup use itself. + +## Validation + +Before pushing to `main`: + +1. `go build ./...` and `go test ./...` pass in the new repo. +2. `golangci-lint run` passes. +3. `grep -r "ep-tools\|C2FO/ep-tools" .` returns nothing (outside `docs/` + historical mentions that we consciously keep, if any). +4. The scaffolding workflows are syntactically valid (YAML parse). +5. A manual local `prenup run` on the new repo succeeds (dogfood). + +## Out of scope + +- Docker image publishing. +- Cross-platform binary release (GoReleaser). +- CONTRIBUTING.md, SECURITY.md, Makefile (neither reference project has them). +- Rewriting the internal `docs/V2_PLAN.md` — will drop it unless there's a + reason to keep it public. +- Any code refactoring beyond what's needed to remove monorepo assumptions. + +## Non-negotiables recap + +- Fresh git history. +- Module path: `github.com/c2fo/prenup` (no `/v2`). +- Changelog resets to `v0.1.0`. +- Scaffolding mirrors releasegen; release workflow mirrors vfs. +- Distribution is `go install`-only. From f2168cdbfb79383ff9b7e22fde5ca200f50aa424 Mon Sep 17 00:00:00 2001 From: John Judd Date: Sat, 4 Jul 2026 15:33:30 -0500 Subject: [PATCH 3/7] ci: remove vfs-specific v6 branch trigger from release workflow --- .github/workflows/releasegen.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/releasegen.yml b/.github/workflows/releasegen.yml index 57318e3..2083184 100644 --- a/.github/workflows/releasegen.yml +++ b/.github/workflows/releasegen.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - v6 workflow_dispatch: inputs: branch: From 5f8a117a4d3d11b6b66d8fa8e531ff01ca22b414 Mon Sep 17 00:00:00 2001 From: John Judd Date: Sat, 4 Jul 2026 17:20:18 -0500 Subject: [PATCH 4/7] initial commit --- .prenup.yaml | 39 ++ .testcoverage.yml | 6 +- AGENTS.md | 345 ++++++++++++++++++ CHANGELOG.md | 11 +- CONTRIBUTING.md | 98 +++++ README.md | 43 ++- SECURITY.md | 47 +++ assets/prenup.schema.json | 8 +- cmd/prenup/e2e_test.go | 36 +- cmd/prenup/main.go | 2 +- docs/{PRD.md => DESIGN.md} | 48 ++- docs/FUTURE.md | 16 +- docs/SCHEMA.md | 8 +- .../2026-07-03-open-source-prenup-design.md | 36 ++ internal/cli/commands_integration_test.go | 168 +++++++++ internal/cli/commands_metadata_test.go | 127 +++++++ internal/cli/config_cmd.go | 2 +- internal/cli/config_cmd_test.go | 113 ++++++ internal/cli/init_test.go | 174 +++++++++ internal/cli/install_helpers_test.go | 118 ++++++ internal/cli/migrate.go | 69 ---- internal/cli/plan_render_test.go | 168 +++++++++ internal/cli/root.go | 1 - internal/cli/run_helpers_test.go | 207 +++++++++++ internal/cli/run_internal_test.go | 4 +- internal/config/config.go | 55 ++- internal/config/config_test.go | 44 ++- internal/config/marshal_test.go | 105 ++++++ internal/config/migrate.go | 76 ---- internal/config/migrate_test.go | 56 --- internal/config/schema.go | 2 +- internal/config/schema.json | 8 +- internal/discover/changes_test.go | 115 ++++++ internal/git/version_test.go | 24 ++ internal/hook/exists_error_test.go | 22 ++ internal/lock/helpers_test.go | 99 +++++ internal/runner/exec_test.go | 133 +++++++ internal/runner/runner_test.go | 8 +- internal/ui/agent_test.go | 50 +++ prenup.example.yaml | 7 +- 40 files changed, 2374 insertions(+), 324 deletions(-) create mode 100644 .prenup.yaml create mode 100644 AGENTS.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md rename docs/{PRD.md => DESIGN.md} (89%) create mode 100644 internal/cli/commands_integration_test.go create mode 100644 internal/cli/commands_metadata_test.go create mode 100644 internal/cli/config_cmd_test.go create mode 100644 internal/cli/init_test.go create mode 100644 internal/cli/install_helpers_test.go delete mode 100644 internal/cli/migrate.go create mode 100644 internal/cli/plan_render_test.go create mode 100644 internal/cli/run_helpers_test.go create mode 100644 internal/config/marshal_test.go delete mode 100644 internal/config/migrate.go delete mode 100644 internal/config/migrate_test.go create mode 100644 internal/discover/changes_test.go create mode 100644 internal/git/version_test.go create mode 100644 internal/hook/exists_error_test.go create mode 100644 internal/lock/helpers_test.go create mode 100644 internal/runner/exec_test.go create mode 100644 internal/ui/agent_test.go diff --git a/.prenup.yaml b/.prenup.yaml new file mode 100644 index 0000000..8a92dcd --- /dev/null +++ b/.prenup.yaml @@ -0,0 +1,39 @@ +# .prenup.yaml — pre-commit checks managed by prenup. +# What is this? Prenup runs configurable checks before each commit, scoped to +# the parts of the repo that changed. Learn more and install: +# https://github.com/c2fo/prenup +version: 1 +module_markers: + - go.mod +exclude: + - .github/** + - '**/*.yaml' + - '**/*.yml' +output: auto +tasks: + - name: Run tests + default_selected: true + command: go test ./... + per_module: true + paths: + - '**/*.go' + - name: Run golangci-lint + default_selected: true + command: golangci-lint run --max-same-issues 0 ./... + per_module: true + paths: + - '**/*.go' + - name: Check for changes in CHANGELOG.md + default_selected: true + # Runs the same image and subcommand the validate workflow uses in CI, + # reading custom_change_types from .releasegen.yaml. Replaces the + # legacy changelog.sh "did the changelog change?" guard. + # + # --pull=always forces Docker to check GHCR for the current digest of + # the `v1` moving tag before each run, so we stay automatically in + # sync with the latest v1.x release that CI uses. Without this flag + # Docker uses its default --pull=missing behavior and will keep + # running whatever was first cached locally, even after newer v1.x + # images ship. Cost is one HTTP HEAD per invocation; layers stay + # cached so re-downloads only happen on a real version bump. + command: docker run --rm --pull=always -v "{{.repo_root}}:/workspace" ghcr.io/c2fo/releasegen:v1 validate --repo-root /workspace diff --git a/.testcoverage.yml b/.testcoverage.yml index 0ff9ab8..1aaae2f 100644 --- a/.testcoverage.yml +++ b/.testcoverage.yml @@ -7,9 +7,9 @@ profile: coverage.out # Coverage thresholds (percentages, 0-100). Starting conservative for v0.1.0; # raise as coverage improves, never lower. threshold: - total: 40 - package: 0 - file: 0 + total: 55 + package: 50 + file: 40 # Exclude generated and non-testable code from coverage statistics. exclude: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c31c549 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,345 @@ +# Agent Guidelines for the Prenup Repository + +This document provides guidelines for AI agents (and humans) working on the +`prenup` repository. It captures the conventions the codebase already follows +so contributions stay consistent. + +## Table of Contents +- [Development Guidelines](#development-guidelines) +- [Testing](#testing) +- [Config Schema Versioning](#config-schema-versioning) +- [CHANGELOG and PR Process](#changelog-and-pr-process) +- [Go Version Policy](#go-version-policy) +- [Dependency Upgrades](#dependency-upgrades) +- [GitHub Actions Maintenance](#github-actions-maintenance) +- [Release Process](#release-process) +- [Repository Structure](#repository-structure) + +--- + +## Development Guidelines + +### Code Style + +- Follow standard Go idioms; run `gofmt` and `goimports`. +- Imports are grouped with `github.com/c2fo/` local — see `.golangci.yml`. +- All exported types, functions, and methods have godoc comments. +- Keep functions small and focused; prefer early returns to reduce nesting. +- **Do not add narrating comments** that restate what the code does + (e.g. `// increment counter`). Comments explain non-obvious intent, + trade-offs, or constraints — not the mechanics. + +### Error Handling + +- Handle every error explicitly; no silent failures. +- Wrap with context: `fmt.Errorf("operation failed: %w", err)`. +- Never ignore return values. +- Config, git, and hook errors are user-facing — write messages that tell the + user what to fix. + +### Interface Design + +- Prefer small, focused interfaces. +- Define interfaces in the *consuming* package, not the implementing one. +- Use dependency injection for testability. The `runner.Executor` and + `runner.Sink` interfaces are the canonical examples. + +### File Organization + +- Group related functionality in packages. One package per major feature. +- Place mocks in `mocks/` subdirectories (see `internal/runner/mocks/`). +- CLI commands live in `internal/cli/`, one file per command. + +### Linting + +- All code must pass `golangci-lint run` with 0 issues. +- Config: `.golangci.yml`. +- CI runs the linter with `--only-new-issues` on PRs. + +--- + +## Testing + +### Coverage requirements + +- **All new code must have tests.** Aim for coverage close to 100% where + practical. +- Minimum thresholds are enforced by CI via `.testcoverage.yml` and the + `go-test-coverage` GitHub Action. +- The thresholds start conservative and **only ratchet up** — never lower a + threshold to make CI pass. If a change lowers coverage, add tests until it + recovers. +- Some code is legitimately excluded from coverage: the interactive Bubble + Tea TUI (`internal/ui/human/`), the trivial output-mode shim + (`internal/ui/output.go`), and `cmd/prenup/main.go`. Update + `.testcoverage.yml` if that list needs to change. + +### Test organization + +- **Use `testify/suite`** for related tests sharing setup/teardown. One suite + per component; naming: `[Component]TestSuite` (e.g. `RunnerTestSuite`). +- Simple unit tests don't require a suite; a plain `TestXxx(t *testing.T)` + is fine for small, standalone cases. +- Use `SetupTest()` / `TearDownTest()` (or `t.TempDir()`) for isolation. + +### Test style + +- **Prefer table-driven tests.** Slice of anonymous structs with `name`, + inputs, and expected outputs. Every case gets a descriptive name. +- Use suite methods (`s.Require()` / `s.Assert()`) inside suites; use the + standalone `require`/`assert` packages outside them. +- Cover both success and error paths in the same table. +- Example (matches the style used throughout the repo): + + ```go + tests := []struct { + name string + input string + expectedOutput string + expectedError string + }{ + {name: "success", input: "valid", expectedOutput: "result"}, + {name: "invalid input", input: "", expectedError: "input is required"}, + } + for _, tt := range tests { + s.Run(tt.name, func() { + got, err := fn(tt.input) + if tt.expectedError != "" { + s.Require().Error(err) + s.Assert().Contains(err.Error(), tt.expectedError) + return + } + s.Require().NoError(err) + s.Assert().Equal(tt.expectedOutput, got) + }) + } + ``` + +### Mocking + +- **Use `mockery` v3** with the EXPECT() pattern for readability and type + safety. Do not hand-write mocks. +- Config lives in `.mockery.yaml`. Mocks land in `mocks/` subdirectories. +- Regenerate after changing a mocked interface: `mockery`. +- Example EXPECT() usage: + + ```go + exec := mocks.NewExecutor(t) + exec.EXPECT(). + Run(mock.Anything, mock.MatchedBy(func(spec runner.ExecSpec) bool { + return spec.Command == "go test ./..." + })). + Return(nil) + ``` + +### Test types + +- **Unit tests** live alongside the code (`foo_test.go` next to `foo.go`). +- **End-to-end tests** live in `cmd/prenup/e2e_test.go`; they build the + binary and drive real `git` repos in `t.TempDir()`. E2E does not + contribute to package coverage (external process), so critical logic + still needs unit tests. +- **Race detection:** `go test -race ./...` runs cleanly. + +### Running the tests + +```bash +go test ./... # full suite +go test -race ./... # with race detector +go test -coverprofile=coverage.out ./... # generate coverage profile +go tool cover -func=coverage.out # per-function coverage +go tool cover -html=coverage.out # browse coverage +``` + +--- + +## Config Schema Versioning + +The `.prenup.yaml` file declares `version:` — currently `1`. This is the +config *schema* version, a plain integer **decoupled from the prenup release +version**. Rules: + +- It bumps **only** on a backward-incompatible change to the file format. +- Additive, non-breaking changes (new optional fields with sensible defaults) + do **not** bump the version — old configs keep working. +- The current version lives in `internal/config/config.go` as + `SchemaVersion`; the JSON Schema files (`internal/config/schema.json` and + `assets/prenup.schema.json`) must stay byte-identical and be updated + together. +- Loading a config with a *newer* version than the binary understands must + fail with a clear "upgrade prenup" message. +- Configs that prenup writes (`prenup init`, future `config upgrade`) go + through `config.Marshal`, which prepends the discoverability header that + links back to the project. Preserve it. +- Do **not** silently rewrite a user's config. Any future format-migration + command must be opt-in. + +## CHANGELOG and PR Process + +### Required for every PR + +Every PR **must** add an entry under the `## [Unreleased]` section of +`CHANGELOG.md`. Do **not** add a version number — versions are assigned at +release time from the changelog contents. + +### CHANGELOG format + +Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Standard +headings: + +- `### Added` — new features +- `### Fixed` — bug fixes +- `### Changed` / `### Removed` — **breaking changes only**; the description + must include the exact phrase `BREAKING CHANGE` +- `### Deprecated` — features slated for removal +- `### Security` — security-related updates + +If a change is not breaking, use `### Added` (new features) or `### Fixed` +(bug fixes) — never `### Changed`. + +### Version bump rules + +The next release version is derived from the `[Unreleased]` sections: + +- **Major** — any `### Changed` or `### Removed` with `BREAKING CHANGE` +- **Minor** — `### Added`, `### Deprecated`, or `### Security` +- **Patch** — only `### Fixed` + +--- + +## Go Version Policy + +Prenup supports the latest Go version and the previous minor version. The +`go.mod` `go` directive pins the older of the two so both keep working. + +### Files to update on a Go version bump + +- `go.mod` — `go` directive +- `.golangci.yml` — `run.go` +- `.github/workflows/ci.yml` — `go-version` +- `.github/workflows/codeql.yml` — `go-version` +- `.github/workflows/golangci-lint.yml` — `go-version` +- `.github/workflows/go-test-coverage.yml` — `go-version` +- `.github/workflows/validate-changelog.yml` — `go-version` +- `.github/workflows/releasegen.yml` — `go-version` if the workflow builds + from source (this repo's release workflow runs a prebuilt image, so no + change usually needed) + +--- + +## Dependency Upgrades + +- Update dependencies regularly for security and bug fixes. +- **Dependency bumps are their own PR** — do not fold them into unrelated + feature or docs work. +- Test with race detection after every bump. +- Document breaking changes in `CHANGELOG.md`. + +```bash +go get -u -t ./... +go mod tidy +go test -race ./... +``` + +--- + +## GitHub Actions Maintenance + +### SHA pinning + +All actions must be pinned to a full 40-character commit SHA, with the tag +name in a trailing comment: + +```yaml +uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +``` + +### Update policy + +- Select the newest version that is **at least 10 days old**. If the latest + is too fresh, pick a slightly older one. +- Verify release dates and check for security advisories before updating. +- Test the change in a PR before merging. + +### Workflow files + +- `ci.yml` — PR test workflow +- `golangci-lint.yml` — PR lint workflow +- `codeql.yml` — security scanning (PR + weekly cron) +- `go-test-coverage.yml` — enforces `.testcoverage.yml` thresholds +- `validate-changelog.yml` — runs `releasegen validate` on PRs +- `releasegen.yml` — cuts tags and GitHub releases from `CHANGELOG.md` on + push to `main`; uses `ghcr.io/c2fo/releasegen:v1` + +--- + +## Release Process + +Releases are automated by the `releasegen.yml` workflow: + +1. Every PR updates `## [Unreleased]` with the change. +2. On merge to `main`, `releasegen` computes the next SemVer from the + `[Unreleased]` entries, promotes them into a versioned section, tags the + commit, and publishes a GitHub Release with the notes. +3. Users install via `go install github.com/c2fo/prenup/cmd/prenup@` or + `@latest`. + +Prenup does not currently ship a Docker image or prebuilt binaries. + +--- + +## Repository Structure + +``` +prenup/ +├── AGENTS.md # this file +├── CHANGELOG.md +├── CODE_OF_CONDUCT.md +├── CONTRIBUTING.md +├── LICENSE.md +├── README.md +├── SECURITY.md +├── go.mod # single Go module +├── .prenup.yaml # prenup dogfoods itself +├── .releasegen.yaml +├── .golangci.yml +├── .testcoverage.yml +├── .mockery.yaml +├── assets/prenup.schema.json # published JSON Schema (byte-identical to internal copy) +├── prenup.example.yaml # user-facing example config +├── cmd/prenup/ # binary entry point + end-to-end tests +│ ├── main.go +│ └── e2e_test.go +├── docs/ # DESIGN.md, SCHEMA.md, FUTURE.md +├── internal/ +│ ├── cli/ # cobra command tree +│ ├── config/ # .prenup.yaml schema + loading + validation +│ ├── discover/ # change + module detection +│ ├── git/ # git subprocess wrapper +│ ├── hook/ # pre-commit hook install/uninstall +│ ├── lock/ # per-repo advisory lock +│ ├── runner/ # task planning + execution +│ │ └── mocks/ # mockery-generated Executor/Sink mocks +│ ├── ui/ # output-mode dispatch +│ │ ├── human/ # Bubble Tea TUI (excluded from coverage) +│ │ ├── jsonout/ # NDJSON event stream +│ │ └── markdown/ # markdown digest +│ └── versioncheck/ # GitHub Releases update checker +└── .github/ + ├── ISSUE_TEMPLATE/ + └── workflows/ +``` + +--- + +## References + +- [Go Release Policy](https://go.dev/doc/devel/release) +- [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) +- [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +- [golangci-lint](https://golangci-lint.run/) +- [testify](https://github.com/stretchr/testify) +- [mockery](https://github.com/vektra/mockery) +- [releasegen](https://github.com/c2fo/releasegen) — computes the next SemVer + from `[Unreleased]` and cuts the tag + GitHub Release +- [Prenup CHANGELOG](CHANGELOG.md) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c73be1..95c765d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.1.0] - 2026-07-03 - ### Added - - Initial open-source release. - Interactive, configuration-driven Git pre-commit hook runner. -- Subcommands: `run`, `plan`, `install`, `uninstall`, `init`, `migrate`, +- Subcommands: `run`, `plan`, `install`, `uninstall`, `init`, `config validate`, `config schema`, `version`. - Per-task path filtering with doublestar globs (`paths`, `paths_ignore`, `exclude`). @@ -32,10 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 timeout, then SIGKILL if the task does not exit. - Template variables in `command` and `working_dir`: `{{.repo_root}}`, `{{.module_root}}`, `{{.module_path}}`, `{{.module_name}}`. -- `prenup migrate` to convert v1 `.prenup.yml` files to the current v2 - schema. -- Embedded JSON Schema for editor `$schema` integration, published at - `assets/prenup.schema.json`. +- Embedded JSON Schema (config `version: 1`) for editor `$schema` integration, + published at `assets/prenup.schema.json`. - GitHub Releases API version check with a one-line update notice; honors `PRENUP_GITHUB_TOKEN`, `GITHUB_TOKEN`, or `GH_TOKEN` for authenticated requests. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bdc7f29 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,98 @@ +# Contributing to Prenup + +Thanks for your interest in improving Prenup! Issues and pull requests are +welcome. This document covers how to get set up and the conventions the +project follows. + +By participating, you agree to abide by our +[Code of Conduct](CODE_OF_CONDUCT.md). + +## Getting started + +Prenup is a single Go module (`github.com/c2fo/prenup`). + +```bash +git clone https://github.com/c2fo/prenup.git +cd prenup +go build ./... +go test ./... +``` + +You need Go 1.26 or newer, plus `git` and `bash` on your `PATH` (Prenup runs +tasks via `bash -c` and shells out to `git`). + +## Development workflow + +1. Fork the repo and create a topic branch off `main`. +2. Make your change, with tests. +3. Run the full local check suite (below). +4. Update `CHANGELOG.md` (see [Changelog](#changelog)). +5. Open a pull request describing the change and its motivation. + +Prenup dogfoods itself: the repo ships a `.prenup.yaml`, so once you run +`prenup install` in your clone the hook will run tests, `golangci-lint`, +`go vet`, and a `gofmt` check on commit. + +## Local checks + +Before opening a PR, make sure these pass: + +```bash +go build ./... +go test ./... +golangci-lint run +gofmt -l . # should print nothing +``` + +## Testing + +- **All new code must have tests.** Aim for coverage as close to 100% as is + practical. +- Use [`testify`](https://github.com/stretchr/testify) — prefer `suite` for + related tests, and `s.Require()` / `s.Assert()` within suites. +- Prefer **table-driven tests** with a slice of named cases. +- Generate mocks with `mockery` v3 (config in `.mockery.yaml`); do not + hand-write mocks. Regenerate with `mockery` after changing a mocked + interface. + +Coverage thresholds are enforced in CI via `.testcoverage.yml`. + +## Code style + +- Run `gofmt` / `goimports`; imports are grouped with the + `github.com/c2fo/` prefix local (see `.golangci.yml`). +- All exported types, functions, and methods need doc comments. +- Handle every error explicitly; wrap with context using + `fmt.Errorf("...: %w", err)`. +- Keep functions small and prefer early returns. +- Do not add comments that merely restate what the code does. + +## Changelog + +Every PR must add an entry under the `## [Unreleased]` section of +[CHANGELOG.md](CHANGELOG.md). The changelog follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Use the appropriate heading for your change: + +- `### Added` — new features +- `### Fixed` — bug fixes +- `### Changed` / `### Removed` — **breaking changes only**; include the exact + phrase `BREAKING CHANGE` in the description +- `### Deprecated` — features slated for removal +- `### Security` — security-related updates + +**Do not add a version number** — versions are assigned automatically at +release time from the changelog contents. + +## Pull request expectations + +- Focused, single-purpose changes are easier to review. +- CI (tests, lint, coverage, CodeQL, changelog validation) must be green. +- Update relevant docs (`README.md`, `docs/`) when behavior changes. + +## Reporting bugs and requesting features + +Use the GitHub issue templates. For security issues, please follow +[SECURITY.md](SECURITY.md) instead of opening a public issue. diff --git a/README.md b/README.md index e8631eb..d9ff97b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ ![prenup.png](prenup.png) +[![GitHub tag](https://img.shields.io/github/tag/c2fo/prenup.svg?style=flat)](https://github.com/c2fo/prenup/releases) +[![GoDoc](https://pkg.go.dev/badge/github.com/c2fo/prenup?utm_source=godoc)](https://pkg.go.dev/github.com/c2fo/prenup) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md) +[![Last Commit](https://img.shields.io/github/last-commit/c2fo/prenup)](https://github.com/c2fo/prenup/commits/main) + # Prenup — Interactive Pre-Commit Hook Utility Prenup runs user-defined tasks (tests, linters, doc generators, custom scripts) @@ -7,10 +12,10 @@ as a Git pre-commit hook. It is designed to be fast, selective, and visible: only the modules that actually changed get checked, task output streams live, and the developer stays in the loop through a simple interactive UI. -Prenup provides dedicated subcommands (`install`, `init`, `run`, `plan`, -`migrate`), per-task path filtering, per-module parallelism, stash-and-restore -for safer checks, and agent-friendly output modes (markdown digest when piped, -NDJSON with `--output json`). +Prenup provides dedicated subcommands (`install`, `init`, `run`, `plan`), +per-task path filtering, per-module parallelism, stash-and-restore for safer +checks, and agent-friendly output modes (markdown digest when piped, NDJSON +with `--output json`). ## Quick start @@ -35,8 +40,7 @@ You commit. Prenup runs. Pass or fail, you see why. | `prenup install` | Install `.git/hooks/pre-commit`. Prompts on conflicts. | | `prenup uninstall` | Remove the managed hook, restoring backups when present. | | `prenup init` | Scaffold a starter `.prenup.yaml` from repo inspection. | -| `prenup migrate` | Convert a v1 `.prenup.yml` into a v2 `.prenup.yaml`. | -| `prenup config validate [path]` | Validate a config against the v2 schema. | +| `prenup config validate [path]` | Validate a config against the schema. | | `prenup config schema` | Print the embedded JSON schema. | | `prenup version` | Print the binary version. | @@ -97,7 +101,7 @@ the `agent_hint` bootstrap line). Configuration lives at the repository root as `.prenup.yaml` (or `.prenup.yml`). ```yaml -version: 2 +version: 1 module_markers: - go.mod exclude: @@ -191,25 +195,23 @@ Failures are silent and never block a commit. For private repositories or to avoid unauthenticated rate limits, set one of `PRENUP_GITHUB_TOKEN`, `GITHUB_TOKEN`, or `GH_TOKEN`. -## Migrating from v1 - -Run `prenup migrate` in the root of a repo with a v1 `.prenup.yml`: +## Config schema versioning -```bash -prenup migrate # writes .prenup.yaml beside .prenup.yml -prenup migrate --in path --out path # explicit locations -``` +`.prenup.yaml` declares a `version:` — currently `1`. This is the config +*schema* version, a plain integer independent of the prenup release version. +It only changes if the file format ever changes in a backward-incompatible +way; additive, non-breaking features do not bump it, so existing configs keep +working across prenup upgrades. -Field names are preserved. New v2 features (`paths`, `paths_ignore`, -`module_markers`, `parallel`, `env`, `clean_worktree`) are not invented for -you; add them manually where useful. `prenup config validate` confirms the -migrated file parses cleanly. +If prenup encounters a config with a `version:` newer than the running binary +understands, it fails with a clear message telling you to upgrade prenup. ## Documentation -- [docs/PRD.md](docs/PRD.md) — product requirements and behavior spec. +- [docs/DESIGN.md](docs/DESIGN.md) — design and behavior specification. - [docs/SCHEMA.md](docs/SCHEMA.md) — config schema and JSON event stream. - [docs/FUTURE.md](docs/FUTURE.md) — deferred improvements. +- [CONTRIBUTING.md](CONTRIBUTING.md) — how to contribute. - [CHANGELOG.md](CHANGELOG.md) — release history. ## Platform support @@ -219,7 +221,8 @@ see [docs/FUTURE.md](docs/FUTURE.md). ## Contributing -Issues and pull requests are welcome. See the [Contributor Covenant Code of +Issues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) +for development setup and conventions, and the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). All PRs must update the `[Unreleased]` section of [CHANGELOG.md](CHANGELOG.md); version numbers are assigned automatically at release time. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..5a3fa1d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,47 @@ +# Security Policy + +## Supported versions + +Prenup is distributed as a single Go binary. Security fixes are applied to the +latest released version. Please make sure you are on the most recent release +before reporting an issue. + +## Reporting a vulnerability + +**Please do not report security vulnerabilities through public GitHub issues, +pull requests, or discussions.** + +Instead, report them privately using GitHub's private vulnerability reporting: + +1. Go to the [Security tab](https://github.com/c2fo/prenup/security) of this + repository. +2. Click **Report a vulnerability**. +3. Provide as much detail as you can, including: + - a description of the issue and its impact, + - steps to reproduce (a proof of concept if possible), + - affected version(s), and + - any suggested remediation. + +We will acknowledge your report, investigate, and keep you informed of the +resolution. Once a fix is available we will publish a release and, where +appropriate, a security advisory crediting the reporter (unless you prefer to +remain anonymous). + +## Scope + +Prenup executes user-defined commands from a repository's `.prenup.yaml` via +`bash`. Configuration files are treated as trusted input controlled by the +repository owner; running Prenup against a repository is equivalent to +trusting that repository's configured commands. Reports that rely on a +maliciously crafted `.prenup.yaml` in a repository you already control are +generally out of scope. + +Vulnerabilities of interest include (but are not limited to): + +- unintended command execution outside of configured tasks, +- path traversal or writing outside the repository during output staging, +- leaking credentials (e.g. `PRENUP_GITHUB_TOKEN`) to task output or logs, +- privilege or worktree-integrity issues in the stash/restore or hook-install + logic. + +Thank you for helping keep Prenup and its users safe. diff --git a/assets/prenup.schema.json b/assets/prenup.schema.json index 31ef3a1..f1d6e96 100644 --- a/assets/prenup.schema.json +++ b/assets/prenup.schema.json @@ -1,16 +1,16 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/c2fo/prenup/main/assets/prenup.schema.json", - "title": "Prenup v2 configuration", - "$comment": "This file is the canonical source. assets/prenup.schema.json must stay byte-identical. When adding a v3, replace `version.const: 2` with a `oneOf` over per-version schemas.", + "title": "Prenup configuration", + "$comment": "This file is the canonical source. assets/prenup.schema.json must stay byte-identical. When adding a version 2, replace `version.const: 1` with a `oneOf` over per-version schemas.", "type": "object", "required": ["version", "tasks"], "additionalProperties": false, "properties": { "version": { "type": "integer", - "const": 2, - "description": "Config schema version. Must be 2." + "const": 1, + "description": "Config schema version. Must be 1." }, "module_markers": { "type": "array", diff --git a/cmd/prenup/e2e_test.go b/cmd/prenup/e2e_test.go index 122ea99..12a1f5b 100644 --- a/cmd/prenup/e2e_test.go +++ b/cmd/prenup/e2e_test.go @@ -95,7 +95,7 @@ func TestE2ERunWithNoChanges(t *testing.T) { dir := t.TempDir() initRepo(t, dir) - cfg := `version: 2 + cfg := `version: 1 tasks: - name: "Echo" command: "echo hello" @@ -119,7 +119,7 @@ func TestE2ERunPassingTaskMarkdownOutput(t *testing.T) { require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) - cfg := `version: 2 + cfg := `version: 1 clean_worktree: false tasks: - name: "Echo" @@ -142,7 +142,7 @@ func TestE2ERunFailingTaskBlocksCommit(t *testing.T) { initRepo(t, dir) require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) - cfg := `version: 2 + cfg := `version: 1 clean_worktree: false tasks: - name: "Fail" @@ -163,7 +163,7 @@ func TestE2ERunJSONOutput(t *testing.T) { initRepo(t, dir) require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) - cfg := `version: 2 + cfg := `version: 1 clean_worktree: false tasks: - name: "Echo" @@ -207,7 +207,7 @@ func TestE2EPlanJSON(t *testing.T) { initRepo(t, dir) require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/x\n\ngo 1.22\n"), 0o600)) - cfg := `version: 2 + cfg := `version: 1 tasks: - name: "Echo" command: "echo hi" @@ -243,36 +243,12 @@ func TestE2EInstallUninstallRoundTrip(t *testing.T) { assert.True(t, os.IsNotExist(err)) } -func TestE2EMigrateV1(t *testing.T) { - bin := buildBinary(t) - dir := t.TempDir() - initRepo(t, dir) - - v1 := `exclude: - - .github -tasks: - - name: "Tests" - default_selected: true - command: "go test ./..." - per_module: true -` - require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yml"), []byte(v1), 0o600)) - - _, exit := runPrenup(t, bin, dir, "migrate") - require.Equal(t, 0, exit) - - data, err := os.ReadFile(filepath.Join(dir, ".prenup.yaml")) //nolint:gosec // G304: path under test TempDir. - require.NoError(t, err) - assert.Contains(t, string(data), "version: 2") - assert.Contains(t, string(data), "Tests") -} - func TestE2EConfigValidate(t *testing.T) { bin := buildBinary(t) dir := t.TempDir() initRepo(t, dir) - cfg := `version: 2 + cfg := `version: 1 tasks: - name: "Echo" command: "echo" diff --git a/cmd/prenup/main.go b/cmd/prenup/main.go index 56fbb9f..e817ef1 100644 --- a/cmd/prenup/main.go +++ b/cmd/prenup/main.go @@ -1,7 +1,7 @@ // Package main is the entry point for the prenup binary. // // Prenup is an interactive, configuration-driven Git pre-commit hook utility. -// See README.md and docs/PRD.md for the product description and docs/ for +// See README.md and docs/DESIGN.md for the product description and docs/ for // reference material (config schema, event schema, future work). package main diff --git a/docs/PRD.md b/docs/DESIGN.md similarity index 89% rename from docs/PRD.md rename to docs/DESIGN.md index 1275fde..22a96d5 100644 --- a/docs/PRD.md +++ b/docs/DESIGN.md @@ -1,4 +1,9 @@ -# Prenup v2 — Product Requirements Document +# Prenup — Design & Behavior Specification + +This document describes what `prenup` does and how it behaves. It is the +reference for the tool's design decisions, configuration surface, and runtime +semantics. For a field-by-field config and event reference, see +[SCHEMA.md](SCHEMA.md). ## 1. Overview @@ -16,14 +21,13 @@ the commit when something genuinely fails. While usable in any repository, Prenup is purpose-built for **Go monorepos and multi-module repositories**, where "what changed" naturally maps to one or -more Go modules. v2 generalizes the module concept via configurable +more Go modules. The module concept is generalized via configurable `module_markers`, opening the door to polyglot repos without breaking the core experience. -v2 is also an explicit acknowledgment that prenup is used both by humans and -by agentic tooling. Output is TUI-first when run interactively, but -auto-degrades to a structured markdown digest when piped and can emit a -stable NDJSON event stream on request. +Prenup is designed to be used both by humans and by agentic tooling. Output is +TUI-first when run interactively, but auto-degrades to a structured markdown +digest when piped and can emit a stable NDJSON event stream on request. ## 2. Goals & Non-Goals @@ -51,7 +55,7 @@ stable NDJSON event stream on request. `.prenup.yaml` and the configured `module_markers`. - Does not author commits or modify staged content beyond optionally `git add`-ing declared output files. -- Does not support Windows in v2. +- Does not support Windows. ## 3. Target Users & Use Cases @@ -161,8 +165,8 @@ pointer to the offending field rather than silently never matching. `prenup config validate` exposes the same check as a standalone command. ### 5.1 Top-level keys -- `version: 2` — required *integer* (not the string `"2"`); guards - against accidentally loading a v1 config. +- `version: 1` — required *integer* (not the string `"1"`). The config schema + version; see [Config schema versioning](#511-config-schema-versioning). - `module_markers` — filenames that define a module. Default `[go.mod]`. - `exclude` — doublestar globs filtering change detection. - `clean_worktree` — stash unstaged changes around task runs. Default `true`. @@ -171,6 +175,28 @@ pointer to the offending field rather than silently never matching. - `output` — default output mode: `auto`, `human`, `markdown`, `json`. - `tasks` — the ordered task list. +#### 5.1.1 Config schema versioning + +The `version` field is the config *schema* version: a plain integer, +deliberately **decoupled from the prenup release version**. It changes only +on a backward-incompatible change to the file format. Additive, non-breaking +changes (e.g. a new optional field) do **not** bump it and require no +migration — old configs keep working because new fields default sensibly. + +Version handling on load: + +- Matching version → parsed normally. +- Newer than the binary understands → rejected with a message directing the + user to upgrade prenup. +- Missing or otherwise unsupported → rejected with an "unsupported version" + error. + +When a future breaking change introduces version 2, prenup will adapt older +configs in memory (so they keep running) and offer an **opt-in** command to +rewrite the file — it will never rewrite a user's config without consent. +That machinery is intentionally deferred until there is a second version to +convert from. + ### 5.2 Per-task fields - `name` *(required)* — display name. - `default_selected` — pre-checked in the selection UI. @@ -280,7 +306,7 @@ The same run produces identical events; the rendering differs: structured markdown digest: - `[prenup] â€Ļ` self-describing preamble identifying the tool, linking to docs, and pointing at the structured (`--output json`) mode - - `## Prenup v2.x.y` header + - `## Prenup vX.Y.Z` header - summary counts - `### Task: ...` sections with status, duration, modules, and tail of output on failure @@ -305,7 +331,7 @@ prenup is and would only see them as noise. - The current directory is inside a git repository. - `.prenup.yaml` lives at the repository root. - Commands run via `bash -c`; shell features are available. -- Linux and macOS only in v2. +- Linux and macOS only. ## 9. Success Criteria diff --git a/docs/FUTURE.md b/docs/FUTURE.md index d99fee6..d233dcc 100644 --- a/docs/FUTURE.md +++ b/docs/FUTURE.md @@ -1,20 +1,19 @@ # Prenup — future work -This file tracks deliberate non-goals of v2 that remain good ideas for -follow-up releases. Nothing here is committed; think of it as a backlog with -rationale. +This file tracks deliberate non-goals that remain good ideas for follow-up +releases. Nothing here is committed; think of it as a backlog with rationale. ## Correctness & reliability ### Continue-on-error within a task -Today (v2), the first failing module in a `per_module` task cancels its +Today, the first failing module in a `per_module` task cancels its remaining modules. Offer an opt-in `fail_fast: false` per task so developers see every failing module in one pass with an aggregated summary. **Acceptance:** per-task config flag; runner collects all module errors; final `task_completed` event carries the aggregate. -### Concurrency safety — shipped in v2 +### Concurrency safety — shipped `prenup run` takes an OS-level advisory lock on `.git/prenup.lock` before executing any tasks. A second concurrent invocation exits non-zero with a clear "another prenup run is already in progress for this repository" @@ -69,7 +68,8 @@ in the post-run summary and a `prenup stats` subcommand for trend analysis. ### Task-level DAG parallelism Add `depends_on` to enable independent tasks to run concurrently while -serializing generators before linters. v2 is per-module-within-task only. +serializing generators before linters. Today's runner is +per-module-within-task only. ### Pluggable module detectors Beyond `module_markers`, introduce named detector plugins (Go, Node, @@ -102,7 +102,7 @@ skip rates. Local-only unless explicitly opted in. ### TUI snapshot tests The Bubble Tea models have intricate state (selection cancel vs skip, runner fail-fast, window resize). Add scripted input + golden-frame tests to prevent -regressions like the cancel-allowed-commit bug historically seen in v1. +regressions such as a cancel action inadvertently allowing the commit. ### Hook chaining edge cases Today `install --chain` runs `pre-commit.local` before prenup. Handle the @@ -159,7 +159,7 @@ Proposed shape (when we do this): ## Explicitly **not** planned -- **Windows support.** Out of scope for v2. Many prenup primitives assume +- **Windows support.** Out of scope. Many prenup primitives assume bash, POSIX paths, and the platform's signal semantics; a Windows port would require a non-trivial rewrite of exec and IO and is not on the roadmap. diff --git a/docs/SCHEMA.md b/docs/SCHEMA.md index cb02412..a35f331 100644 --- a/docs/SCHEMA.md +++ b/docs/SCHEMA.md @@ -1,7 +1,7 @@ -# Prenup v2 schemas +# Prenup schemas This document describes the two stable contracts prenup exposes: the config -file schema and the JSON event stream schema. +file schema (currently at `version: 1`) and the JSON event stream schema. ## Config schema @@ -14,7 +14,7 @@ validation against it. | Field | Type | Default | Description | |---|---|---|---| -| `version` | integer | — (required) | Must be the integer `2`. Quoted strings (`"2"`) are rejected with a hint. | +| `version` | integer | — (required) | Config schema version; must be the integer `1`. Quoted strings (`"1"`) are rejected with a hint. | | `module_markers` | string[] | `[go.mod]` | Filenames whose presence marks a module directory. | | `exclude` | string[] | `[]` | Doublestar globs filtering change detection. | | `clean_worktree` | bool | `true` | Stash unstaged changes around task execution. | @@ -83,7 +83,7 @@ pretty-printed JSON document. See "Plan output" below. #### `run_started` ```json -{"type":"run_started","time":"...","version":"v2.0.0","repo_root":"/path/to/repo","modules":["pkg/foo"],"tasks":["Run tests"],"message":"Update available v2.0.1 ..."} +{"type":"run_started","time":"...","version":"v0.1.0","repo_root":"/path/to/repo","modules":["pkg/foo"],"tasks":["Run tests"],"message":"Update available v0.2.0 ..."} ``` - `repo_root` — absolute path of the git repository prenup was invoked from. Anchors every subsequent task's `working_dir` so consumers do diff --git a/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md b/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md index cccd848..b4d3933 100644 --- a/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md +++ b/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md @@ -159,6 +159,42 @@ Before pushing to `main`: reason to keep it public. - Any code refactoring beyond what's needed to remove monorepo assumptions. +## Addendum (2026-07-04): config versioning redesign + +Decisions made after the initial extraction, before the first public commit: + +1. **Drop the `migrate` command.** The v1→v2 config migration only ever + applied to the internal ep-tools format; no public v1 config exists. Remove + `internal/cli/migrate.go`, `internal/config/migrate.go` (+ tests), the CLI + registration, and all migration docs. Relocate the still-needed `Marshal` + helper from `migrate.go` into `config.go`. + +2. **Reset the public config baseline to `version: 1`.** The first public + config format is version 1 (was `version: 2` from the internal history). + Update the schema (`const: 1`), validator, defaults, example config, and + docs. + +3. **Config schema version is a plain integer, decoupled from the prenup + release version.** It increments *only* on a breaking config-format change. + Additive, non-breaking changes require neither a version bump nor a + migration — new optional fields default sensibly and old configs keep + working. + +4. **Version handling now (minimal):** + - Accept `version: 1`. + - Reject a *newer* version (e.g. `version: 2` seen by an older binary) with + a clear "this config requires a newer version of prenup" error. + - Reject unknown/missing/older-than-1 with the existing "unsupported + version" style error. + - Defer any in-memory adaptation layer and a `config upgrade` command until + `version: 2` actually exists (nothing to convert from yet). The policy is + documented in DESIGN.md so the contract is set. + +5. **Discoverability header.** Scaffolded configs (`prenup init`) lead with a + comment block identifying the tool and linking to the project, so anyone + who encounters a `.prenup.yaml` can find out what it is. If prenup ever + rewrites a config, it must re-emit this header. + ## Non-negotiables recap - Fresh git history. diff --git a/internal/cli/commands_integration_test.go b/internal/cli/commands_integration_test.go new file mode 100644 index 0000000..e502482 --- /dev/null +++ b/internal/cli/commands_integration_test.go @@ -0,0 +1,168 @@ +package cli + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/suite" +) + +// CommandsIntegrationTestSuite drives runInstall, runPlan, and root Execute +// against a real temp git repo. Each of these needs the same fixture +// (git init + chdir), so a suite keeps setup DRY. +type CommandsIntegrationTestSuite struct { + suite.Suite + repo string +} + +func (s *CommandsIntegrationTestSuite) SetupTest() { + dir := s.T().TempDir() + for _, args := range [][]string{ + {"init", "-b", "main"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "test"}, + {"config", "commit.gpgsign", "false"}, + } { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed binary, internal args. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git %s: %s", strings.Join(args, " "), string(out)) + } + // Some dev environments have a global `init.templateDir` or + // `core.hooksPath` that seeds .git/hooks/ with existing hook scripts + // on `git init`. Clear the hooks directory so the install-conflict + // tests can control whether a pre-commit exists. + hooksDir := filepath.Join(dir, ".git", "hooks") + entries, err := os.ReadDir(hooksDir) + if err == nil { + for _, e := range entries { + _ = os.Remove(filepath.Join(hooksDir, e.Name())) + } + } + s.repo = dir + s.T().Chdir(dir) +} + +// TestRunInstall_HappyPath verifies the fresh-repo install: no existing +// pre-commit hook, so no conflict resolution is needed and the hook file is +// created. +func (s *CommandsIntegrationTestSuite) TestRunInstall_HappyPath() { + cmd := newInstallCmd() + s.Require().NoError(runInstall(cmd)) + + info, err := os.Stat(filepath.Join(s.repo, ".git", "hooks", "pre-commit")) + s.Require().NoError(err) + s.False(info.IsDir()) + s.NotZero(info.Mode()&0o100, "hook should be executable") +} + +// TestInstallWithConflictResolution_NonInteractiveFailsFast pins the +// documented CI/scripting contract: if a hook already exists, --non-interactive +// (or a non-TTY stdin) surfaces the conflict as an error rather than +// hanging on a prompt. +func (s *CommandsIntegrationTestSuite) TestInstallWithConflictResolution_NonInteractiveFailsFast() { + hooksDir := filepath.Join(s.repo, ".git", "hooks") + s.Require().NoError(os.MkdirAll(hooksDir, 0o750)) + s.Require().NoError(os.WriteFile(filepath.Join(hooksDir, "pre-commit"), + []byte("#!/bin/sh\necho existing\n"), 0o600)) + + err := installWithConflictResolution(s.repo, "/tmp/prenup", installFlags{nonInteractive: true}) + s.Require().Error(err) + s.Contains(err.Error(), "already exists") +} + +// TestInstallWithConflictResolution_ForceReplaces confirms --force bypasses +// the conflict prompt path entirely: hook.Install returns nil (no +// ExistsError), so the conflict-resolution path is a straight passthrough. +func (s *CommandsIntegrationTestSuite) TestInstallWithConflictResolution_ForceReplaces() { + hooksDir := filepath.Join(s.repo, ".git", "hooks") + s.Require().NoError(os.MkdirAll(hooksDir, 0o750)) + s.Require().NoError(os.WriteFile(filepath.Join(hooksDir, "pre-commit"), + []byte("#!/bin/sh\necho existing\n"), 0o600)) + + err := installWithConflictResolution(s.repo, "/tmp/prenup", installFlags{force: true}) + s.Require().NoError(err) + + data, err := os.ReadFile(filepath.Join(hooksDir, "pre-commit")) //nolint:gosec // G304: test-controlled path inside t.TempDir(). + s.Require().NoError(err) + s.Contains(string(data), "prenup", "force should replace the existing hook body") +} + +// TestRunPlan_NoConfigProducesHelpfulError makes sure `prenup plan` in a +// repo without a .prenup.yaml points the user at what to do. +func (s *CommandsIntegrationTestSuite) TestRunPlan_NoConfigProducesHelpfulError() { + cmd := newPlanCmd() + err := runPlan(cmd) + s.Require().Error(err) + s.Contains(err.Error(), "no .prenup.yaml found") +} + +// TestRunPlan_WithConfigRenders exercises the successful text-render path +// against a minimal in-repo config. +func (s *CommandsIntegrationTestSuite) TestRunPlan_WithConfigRenders() { + cfg := `version: 1 +tasks: + - name: "Echo" + command: "echo hi" + default_selected: true +` + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, ".prenup.yaml"), []byte(cfg), 0o600)) + + // Stage a file so change discovery finds something and BuildPlan has + // modules to fan out over (or a "." fallback). + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "hello.txt"), []byte("hi"), 0o600)) + add := exec.Command("git", "add", "hello.txt") + add.Dir = s.repo + s.Require().NoError(add.Run()) + + out := captureStdout(s.T(), func() error { + return runPlan(newPlanCmd()) + }) + s.Contains(out, "Repository:") + s.Contains(out, "Echo") +} + +// TestRunPlan_JSONMode covers the --output json branch. +func (s *CommandsIntegrationTestSuite) TestRunPlan_JSONMode() { + cfg := `version: 1 +tasks: + - name: "Echo" + command: "echo hi" + default_selected: true +` + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, ".prenup.yaml"), []byte(cfg), 0o600)) + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "hello.txt"), []byte("hi"), 0o600)) + add := exec.Command("git", "add", "hello.txt") + add.Dir = s.repo + s.Require().NoError(add.Run()) + + cmd := newPlanCmd() + s.Require().NoError(cmd.Flags().Set("output", "json")) + + out := captureStdout(s.T(), func() error { return runPlan(cmd) }) + s.Contains(out, `"repo_root"`) + s.Contains(out, `"Echo"`) +} + +// TestExecute_HelpReturnsZero uses the Execute entry point with --help to +// exercise the top-level plumbing without triggering any subcommand's +// heavy work. It's an integration smoke test: os.Args → Execute → 0. +func (s *CommandsIntegrationTestSuite) TestExecute_HelpReturnsZero() { + origArgs := os.Args + s.T().Cleanup(func() { os.Args = origArgs }) + os.Args = []string{"prenup", "--help"} + + // Execute writes usage to stdout; capture it so the test output stays + // clean. + _ = captureStdout(s.T(), func() error { + s.Equal(0, Execute()) + return nil + }) +} + +func TestCommandsIntegrationSuite(t *testing.T) { + suite.Run(t, new(CommandsIntegrationTestSuite)) +} diff --git a/internal/cli/commands_metadata_test.go b/internal/cli/commands_metadata_test.go new file mode 100644 index 0000000..2d7d119 --- /dev/null +++ b/internal/cli/commands_metadata_test.go @@ -0,0 +1,127 @@ +package cli + +import ( + "errors" + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCommandConstructorsSetMetadata asserts that every cobra command +// constructor returns a command with a non-empty Use, Short, and any flags +// the CLI advertises. Metadata regressions (accidentally dropping a flag, +// renaming Use) are caught here without shelling out to the built binary. +func TestCommandConstructorsSetMetadata(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + build func() *cobra.Command + wantUse string + wantFlags []string + }{ + { + name: "root", + build: newRootCmd, + wantUse: "prenup", + wantFlags: []string{ + "config", "output", "task", "all", + "no-interactive", "no-clean-worktree", "parallelism", "dry-run", + }, + }, + { + name: "run", + build: newRunCmd, + wantUse: "run", + wantFlags: []string{ + "config", "output", "task", "all", + "no-interactive", "no-clean-worktree", "parallelism", "dry-run", + }, + }, + {name: "plan", build: newPlanCmd, wantUse: "plan", wantFlags: []string{"config", "output", "all"}}, + { + name: "install", + build: newInstallCmd, + wantUse: "install", + wantFlags: []string{ + "force", "replace", "chain", "binary", "use-path", "non-interactive", + }, + }, + {name: "uninstall", build: newUninstallCmd, wantUse: "uninstall"}, + {name: "init", build: newInitCmd, wantUse: "init", wantFlags: []string{"force"}}, + {name: "version", build: newVersionCmd, wantUse: "version"}, + {name: "config", build: newConfigCmd, wantUse: "config"}, + {name: "config validate", build: newConfigValidateCmd, wantUse: "validate [path]"}, + {name: "config schema", build: newConfigSchemaCmd, wantUse: "schema"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + is := assert.New(t) + must := require.New(t) + + cmd := tc.build() + must.NotNil(cmd, "constructor returned nil") + is.Equal(tc.wantUse, cmd.Use) + is.NotEmpty(cmd.Short, "Short description should not be empty") + + for _, flag := range tc.wantFlags { + is.NotNil(cmd.Flags().Lookup(flag), "expected flag %q", flag) + } + }) + } +} + +// TestRootCommandWiresSubcommands guards against a subcommand being +// dropped from the command tree. +func TestRootCommandWiresSubcommands(t *testing.T) { + t.Parallel() + + root := newRootCmd() + got := map[string]bool{} + for _, c := range root.Commands() { + got[c.Name()] = true + } + + for _, want := range []string{"run", "plan", "install", "uninstall", "init", "config", "version"} { + assert.Truef(t, got[want], "root should expose %q subcommand; got %v", want, got) + } + assert.False(t, got["migrate"], "migrate command should have been removed for v0.1.0") +} + +// TestConfigCommandWiresSubcommands pins the two `config` children. +func TestConfigCommandWiresSubcommands(t *testing.T) { + t.Parallel() + + cfg := newConfigCmd() + got := map[string]bool{} + for _, c := range cfg.Commands() { + got[c.Name()] = true + } + assert.True(t, got["validate"], "config validate should be registered") + assert.True(t, got["schema"], "config schema should be registered") +} + +// TestExitCodeError_Error covers both branches: with a wrapped err (returns +// the wrapped message) and without one (returns a generic "exit status N"). +func TestExitCodeError_Error(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + err *exitCodeError + want string + }{ + {name: "wrapped error message wins", err: &exitCodeError{code: 42, err: errors.New("boom")}, want: "boom"}, + {name: "no wrapped error falls back to code", err: &exitCodeError{code: 3}, want: "exit status 3"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, tc.err.Error()) + }) + } +} diff --git a/internal/cli/config_cmd.go b/internal/cli/config_cmd.go index 8420e31..4a007b0 100644 --- a/internal/cli/config_cmd.go +++ b/internal/cli/config_cmd.go @@ -23,7 +23,7 @@ func newConfigCmd() *cobra.Command { func newConfigValidateCmd() *cobra.Command { cmd := &cobra.Command{ Use: "validate [path]", - Short: "Validate a .prenup.yaml file against the v2 schema", + Short: "Validate a .prenup.yaml file against the schema", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { repoRoot, err := git.RepoRoot("") diff --git a/internal/cli/config_cmd_test.go b/internal/cli/config_cmd_test.go new file mode 100644 index 0000000..7bfba30 --- /dev/null +++ b/internal/cli/config_cmd_test.go @@ -0,0 +1,113 @@ +package cli + +import ( + "encoding/json" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/suite" +) + +// ConfigCommandTestSuite exercises the `prenup config validate` and `prenup +// config schema` handlers end-to-end. Both need a git repo (validate uses +// git.RepoRoot to anchor path lookups; schema does not, but sharing the +// fixture is cheaper than a second suite). +type ConfigCommandTestSuite struct { + suite.Suite + repo string +} + +func (s *ConfigCommandTestSuite) SetupTest() { + dir := s.T().TempDir() + for _, args := range [][]string{ + {"init", "-b", "main"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "test"}, + {"config", "commit.gpgsign", "false"}, + } { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed binary, internal args. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git %s: %s", strings.Join(args, " "), string(out)) + } + s.repo = dir + s.T().Chdir(dir) +} + +// TestValidate_WithInRepoConfig covers the discover-then-parse path: +// `prenup config validate` (no args) finds .prenup.yaml at the repo root +// and reports OK. +func (s *ConfigCommandTestSuite) TestValidate_WithInRepoConfig() { + cfg := `version: 1 +tasks: + - name: "Echo" + command: "echo hi" +` + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, ".prenup.yaml"), []byte(cfg), 0o600)) + + out := captureStdout(s.T(), func() error { + return newConfigValidateCmd().RunE(nil, nil) + }) + s.Contains(out, "OK:") + s.Contains(out, "v1,") + s.Contains(out, "1 tasks") +} + +// TestValidate_ExplicitPathArg covers the explicit-arg branch: passing a +// path directly bypasses discovery. +func (s *ConfigCommandTestSuite) TestValidate_ExplicitPathArg() { + explicit := filepath.Join(s.repo, "custom.yaml") + cfg := `version: 1 +tasks: + - name: "Explicit" + command: "true" +` + s.Require().NoError(os.WriteFile(explicit, []byte(cfg), 0o600)) + + out := captureStdout(s.T(), func() error { + return newConfigValidateCmd().RunE(nil, []string{explicit}) + }) + s.Contains(out, "OK:") + s.Contains(out, "Explicit") +} + +// TestValidate_MissingConfig surfaces a helpful error when there is no +// .prenup.yaml at the repo root and no explicit path was given. +func (s *ConfigCommandTestSuite) TestValidate_MissingConfig() { + err := newConfigValidateCmd().RunE(nil, nil) + s.Require().Error(err) + s.Contains(err.Error(), "no config file found") +} + +// TestValidate_InvalidConfigSurfacesParseError guards the parse-error +// pass-through: a schema violation must reach the user, not silently pass. +func (s *ConfigCommandTestSuite) TestValidate_InvalidConfigSurfacesParseError() { + invalid := filepath.Join(s.repo, "bad.yaml") + s.Require().NoError(os.WriteFile(invalid, []byte(`version: "1"`+"\n"), 0o600)) + + err := newConfigValidateCmd().RunE(nil, []string{invalid}) + s.Require().Error(err) + // The exact message comes from config.Parse's versionTypeHint. + s.Contains(err.Error(), "version") +} + +// TestSchema_PrintsEmbeddedJSONSchema pins that `prenup config schema` emits +// the JSON schema embedded in the binary; the output must parse as JSON and +// declare the right title. +func (s *ConfigCommandTestSuite) TestSchema_PrintsEmbeddedJSONSchema() { + out := captureStdout(s.T(), func() error { + return newConfigSchemaCmd().RunE(nil, nil) + }) + + var schema map[string]any + s.Require().NoError(json.Unmarshal([]byte(out), &schema)) + s.Contains(schema, "$schema") + s.Contains(schema, "properties") +} + +func TestConfigCommandSuite(t *testing.T) { + suite.Run(t, new(ConfigCommandTestSuite)) +} diff --git a/internal/cli/init_test.go b/internal/cli/init_test.go new file mode 100644 index 0000000..566822c --- /dev/null +++ b/internal/cli/init_test.go @@ -0,0 +1,174 @@ +package cli + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +// TestScanForFile is a truth-table for the helper that answers "does any of +// these names exist at the repo root?". +func TestScanForFile(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "Makefile"), []byte("all:\n"), 0o600)) + + cases := []struct { + name string + names []string + want bool + }{ + {name: "exact match found", names: []string{"Makefile"}, want: true}, + {name: "no match", names: []string{"nope.yml"}, want: false}, + {name: "second candidate matches", names: []string{"missing", "Makefile"}, want: true}, + {name: "empty candidate list", names: nil, want: false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, scanForFile(dir, tc.names...)) + }) + } +} + +// TestScanForGoMod covers presence/absence and the pruning rules (hidden +// dirs, vendor/, node_modules/). Rather than a table we just build a couple +// of directory shapes since the layout is the interesting part. +func TestScanForGoMod(t *testing.T) { + t.Parallel() + + t.Run("finds nested go.mod", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + nested := filepath.Join(dir, "pkg", "sub") + require.NoError(t, os.MkdirAll(nested, 0o750)) + require.NoError(t, os.WriteFile(filepath.Join(nested, "go.mod"), []byte("module x\n"), 0o600)) + assert.True(t, scanForGoMod(dir)) + }) + + t.Run("returns false when only go.mod under vendor/", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + vend := filepath.Join(dir, "vendor", "example.com", "y") + require.NoError(t, os.MkdirAll(vend, 0o750)) + require.NoError(t, os.WriteFile(filepath.Join(vend, "go.mod"), []byte("module y\n"), 0o600)) + assert.False(t, scanForGoMod(dir), "vendor/ should be pruned") + }) + + t.Run("returns false in empty dir", func(t *testing.T) { + t.Parallel() + assert.False(t, scanForGoMod(t.TempDir())) + }) +} + +// InitCommandTestSuite exercises runInit end-to-end. Each test needs a +// freshly-initialized git repo and a chdir into it (because `prenup init` +// calls git.RepoRoot("") which is CWD-relative). A suite with SetupTest +// covers both cheaply. +type InitCommandTestSuite struct { + suite.Suite + repo string +} + +func (s *InitCommandTestSuite) SetupTest() { + dir := s.T().TempDir() + for _, args := range [][]string{ + {"init", "-b", "main"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "test"}, + {"config", "commit.gpgsign", "false"}, + } { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed binary, internal args. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git %s: %s", strings.Join(args, " "), string(out)) + } + s.repo = dir + s.T().Chdir(dir) +} + +// TestRunInit_ScaffoldsHeader_and_Version verifies the two public contracts +// the fresh-init output must always satisfy: (1) the discoverability header +// linking to the project, and (2) the correct schema version. +func (s *InitCommandTestSuite) TestRunInit_ScaffoldsHeader_and_Version() { + cmd := newInitCmd() + s.Require().NoError(runInit(cmd)) + + data, err := os.ReadFile(filepath.Join(s.repo, ".prenup.yaml")) + s.Require().NoError(err) + got := string(data) + + s.Contains(got, "https://github.com/c2fo/prenup", + "scaffolded config must advertise the project URL") + s.Contains(got, "version: 1", + "scaffolded config must declare the current schema version") + s.Contains(got, "tasks:") +} + +// TestRunInit_TailorsToRepoContents demonstrates that init responds to what +// it finds in the repo: a go.mod triggers a "Run tests" task, and a +// .golangci.yml triggers a lint task. +func (s *InitCommandTestSuite) TestRunInit_TailorsToRepoContents() { + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "go.mod"), []byte("module example.com/x\n"), 0o600)) + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, ".golangci.yml"), []byte("run: {}\n"), 0o600)) + + cmd := newInitCmd() + s.Require().NoError(runInit(cmd)) + + data, err := os.ReadFile(filepath.Join(s.repo, ".prenup.yaml")) + s.Require().NoError(err) + got := string(data) + + s.Contains(got, "Run tests") + s.Contains(got, "go test ./...") + s.Contains(got, "Run golangci-lint") + s.Contains(got, "golangci-lint run") +} + +// TestRunInit_ExampleTask_WhenRepoIsEmpty covers the fallback branch: when +// there's no go.mod / Makefile, init still produces a valid file with a +// placeholder task. +func (s *InitCommandTestSuite) TestRunInit_ExampleTask_WhenRepoIsEmpty() { + cmd := newInitCmd() + s.Require().NoError(runInit(cmd)) + + data, err := os.ReadFile(filepath.Join(s.repo, ".prenup.yaml")) + s.Require().NoError(err) + s.Contains(string(data), "Example task") +} + +// TestRunInit_RefusesToOverwrite_WithoutForce guards the safety rule: a +// second init against a repo that already has .prenup.yaml must fail unless +// --force is set. +func (s *InitCommandTestSuite) TestRunInit_RefusesToOverwrite_WithoutForce() { + first := newInitCmd() + s.Require().NoError(runInit(first)) + + second := newInitCmd() + err := runInit(second) + s.Require().Error(err) + s.Contains(err.Error(), "already exists") + s.Contains(err.Error(), "--force") +} + +// TestRunInit_ForceOverwrites verifies --force lets a re-run replace the +// prior scaffold. +func (s *InitCommandTestSuite) TestRunInit_ForceOverwrites() { + first := newInitCmd() + s.Require().NoError(runInit(first)) + + second := newInitCmd() + s.Require().NoError(second.Flags().Set("force", "true")) + s.Require().NoError(runInit(second)) +} + +func TestInitCommandSuite(t *testing.T) { + suite.Run(t, new(InitCommandTestSuite)) +} diff --git a/internal/cli/install_helpers_test.go b/internal/cli/install_helpers_test.go new file mode 100644 index 0000000..7b905c0 --- /dev/null +++ b/internal/cli/install_helpers_test.go @@ -0,0 +1,118 @@ +package cli + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/c2fo/prenup/internal/hook" +) + +// TestInstallModeFromFlags is a truth table for the flag → hook.Mode mapping. +// force wins over replace wins over chain wins over abort (default). +func TestInstallModeFromFlags(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + flags installFlags + want hook.Mode + }{ + {name: "no flags → abort (safe default)", flags: installFlags{}, want: hook.ModeAbort}, + {name: "force wins", flags: installFlags{force: true, replace: true, chain: true}, want: hook.ModeForce}, + {name: "replace beats chain", flags: installFlags{replace: true, chain: true}, want: hook.ModeReplace}, + {name: "chain only", flags: installFlags{chain: true}, want: hook.ModeChain}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, installModeFromFlags(tc.flags)) + }) + } +} + +// TestResolveInstallBinary covers the precedence rules for picking what to +// embed in the hook script: +// - --use-path + --binary are mutually exclusive (error) +// - --use-path alone → literal "prenup" (PATH-resolved at hook run time) +// - --binary alone → that path verbatim +// - no flags → falls through to resolveBinary(), which returns something +// non-empty (the currently running test binary) +func TestResolveInstallBinary(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + flags installFlags + wantExact string // when non-empty, require exact match + wantErr string + wantNonEmpty bool + }{ + {name: "use-path and binary conflict", flags: installFlags{usePath: true, binary: "/x"}, wantErr: "mutually exclusive"}, + {name: "use-path alone", flags: installFlags{usePath: true}, wantExact: "prenup"}, + {name: "explicit binary path", flags: installFlags{binary: "/custom/prenup"}, wantExact: "/custom/prenup"}, + {name: "default falls through to resolveBinary", flags: installFlags{}, wantNonEmpty: true}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got, err := resolveInstallBinary(tc.flags) + if tc.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + return + } + require.NoError(t, err) + if tc.wantExact != "" { + assert.Equal(t, tc.wantExact, got) + return + } + if tc.wantNonEmpty { + assert.NotEmpty(t, got) + } + }) + } +} + +// TestReadInstallFlags round-trips every install flag through a fresh cobra +// command to catch drift between the flag registration in newInstallCmd and +// the reader in readInstallFlags. +func TestReadInstallFlags(t *testing.T) { + t.Parallel() + + cmd := newInstallCmd() + must := require.New(t) + + must.NoError(cmd.Flags().Set("force", "true")) + must.NoError(cmd.Flags().Set("replace", "true")) + must.NoError(cmd.Flags().Set("chain", "true")) + must.NoError(cmd.Flags().Set("binary", "/opt/prenup")) + must.NoError(cmd.Flags().Set("use-path", "true")) + must.NoError(cmd.Flags().Set("non-interactive", "true")) + + got := readInstallFlags(cmd) + assert.Equal(t, installFlags{ + force: true, + replace: true, + chain: true, + binary: "/opt/prenup", + usePath: true, + nonInteractive: true, + }, got) +} + +// TestResolveBinaryReturnsAbsolute confirms the default (os.Executable) path +// works in a test binary context. We can't easily pin the exact path since +// `go test` builds a temp binary, but we can check it's non-empty and +// absolute. +func TestResolveBinaryReturnsAbsolute(t *testing.T) { + t.Parallel() + got, err := resolveBinary() + require.NoError(t, err) + assert.NotEmpty(t, got) + assert.True(t, filepath.IsAbs(got), "expected absolute path, got %q", got) +} diff --git a/internal/cli/migrate.go b/internal/cli/migrate.go deleted file mode 100644 index e760444..0000000 --- a/internal/cli/migrate.go +++ /dev/null @@ -1,69 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/spf13/cobra" - - "github.com/c2fo/prenup/internal/config" - "github.com/c2fo/prenup/internal/git" -) - -// newMigrateCmd converts a v1 config into a v2 config. -func newMigrateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "migrate", - Short: "Convert a v1 .prenup.yml to a v2 .prenup.yaml", - RunE: func(cmd *cobra.Command, args []string) error { - return runMigrate(cmd) - }, - } - cmd.Flags().String("in", "", "input v1 config path (defaults to .prenup.yml in repo root)") - cmd.Flags().String("out", "", "output v2 config path (defaults to .prenup.yaml in repo root)") - cmd.Flags().Bool("force", false, "overwrite an existing output file") - return cmd -} - -func runMigrate(cmd *cobra.Command) error { - in, _ := cmd.Flags().GetString("in") - out, _ := cmd.Flags().GetString("out") - force, _ := cmd.Flags().GetBool("force") - - repoRoot, err := git.RepoRoot("") - if err != nil { - return err - } - - if in == "" { - in = filepath.Join(repoRoot, ".prenup.yml") - } - if out == "" { - out = filepath.Join(repoRoot, ".prenup.yaml") - } - - in = filepath.Clean(in) - data, err := os.ReadFile(in) - if err != nil { - return fmt.Errorf("reading %s: %w", in, err) - } - - _, v2bytes, err := config.MigrateV1(data) - if err != nil { - return err - } - - out = filepath.Clean(out) - if _, statErr := os.Stat(out); statErr == nil && !force { - return fmt.Errorf("%s already exists (use --force to overwrite)", out) - } - // out is a user-supplied destination path, cleaned above. - if err := os.WriteFile(out, v2bytes, 0o600); err != nil { - return fmt.Errorf("writing %s: %w", out, err) - } - if _, err := fmt.Fprintf(os.Stdout, "Migrated %s -> %s\n", in, out); err != nil { - return err - } - return nil -} diff --git a/internal/cli/plan_render_test.go b/internal/cli/plan_render_test.go new file mode 100644 index 0000000..e4f8e35 --- /dev/null +++ b/internal/cli/plan_render_test.go @@ -0,0 +1,168 @@ +package cli + +import ( + "encoding/json" + "io" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/c2fo/prenup/internal/config" + "github.com/c2fo/prenup/internal/runner" +) + +// PlanRenderTestSuite groups tests that share a canonical Plan fixture. The +// two rendering functions produce very different output formats, so a suite +// with a common `plan` field keeps each case focused on the format contract +// rather than fixture setup. +type PlanRenderTestSuite struct { + suite.Suite + plan runner.Plan +} + +func (s *PlanRenderTestSuite) SetupTest() { + // A plan with one selected per-module task, one skipped task with a + // reason, and one selected global task exercises every branch inside + // both renderers. + s.plan = runner.Plan{ + RepoRoot: "/repo", + Modules: []string{"pkg/a", "pkg/b"}, + Tasks: []runner.PlannedTask{ + { + Task: config.Task{ + Name: "Run tests", + Command: "go test ./...", + PerModule: true, + }, + Selected: true, + CleanWorktree: true, + Parallel: true, + Modules: []string{"pkg/a", "pkg/b"}, + }, + { + Task: config.Task{ + Name: "Lint (skipped)", + Command: "golangci-lint run", + }, + Selected: false, + SkipReason: "no matching files", + Modules: []string{""}, // renderPlanJSON normalizes this to nil + }, + { + Task: config.Task{ + Name: "Format check", + Command: "gofmt -l .", + }, + Selected: true, + Modules: nil, + }, + }, + } +} + +// TestRenderPlanText_ContainsHeaderAndTaskLines pins the human-readable text +// contract: header lines for the repo + modules, then a checklist-style block +// per task. +func (s *PlanRenderTestSuite) TestRenderPlanText_ContainsHeaderAndTaskLines() { + out := s.captureStdout(func(f *os.File) error { + return renderPlanText(s.plan, f) + }) + + s.Contains(out, "Repository: /repo") + s.Contains(out, "Modules: pkg/a, pkg/b") + s.Contains(out, "[v] Run tests") + s.Contains(out, "per_module, modules: pkg/a, pkg/b") + s.Contains(out, "command: go test ./...") + s.Contains(out, "[-] Lint (skipped)") + s.Contains(out, "skipped: no matching files") + s.Contains(out, "[v] Format check") +} + +// TestRenderPlanJSON_ProducesValidObject asserts the JSON contract: valid +// JSON, top-level repo_root + modules + tasks, and the SkipReason / +// per-module modules normalization is honored. +func (s *PlanRenderTestSuite) TestRenderPlanJSON_ProducesValidObject() { + out := s.captureStdout(func(_ *os.File) error { + return renderPlanJSON(s.plan) + }) + + var got struct { + RepoRoot string `json:"repo_root"` + Modules []string `json:"modules"` + Tasks []struct { + Name string `json:"name"` + Selected bool `json:"selected"` + SkipReason string `json:"skip_reason,omitempty"` + PerModule bool `json:"per_module"` + CleanWorktree bool `json:"clean_worktree"` + Parallel bool `json:"parallel"` + Modules []string `json:"modules"` + } `json:"tasks"` + } + s.Require().NoError(json.Unmarshal([]byte(out), &got)) + + s.Equal("/repo", got.RepoRoot) + s.Equal([]string{"pkg/a", "pkg/b"}, got.Modules) + s.Require().Len(got.Tasks, 3) + + s.Equal("Run tests", got.Tasks[0].Name) + s.True(got.Tasks[0].Selected) + s.True(got.Tasks[0].PerModule) + s.Equal([]string{"pkg/a", "pkg/b"}, got.Tasks[0].Modules) + + s.Equal("Lint (skipped)", got.Tasks[1].Name) + s.False(got.Tasks[1].Selected) + s.Equal("no matching files", got.Tasks[1].SkipReason) + // A single "" entry is a runner sentinel for "no modules"; JSON output + // normalizes it to a missing/empty array so consumers don't see [""]. + s.Empty(got.Tasks[1].Modules) + + s.Equal("Format check", got.Tasks[2].Name) + s.True(got.Tasks[2].Selected) + s.Empty(got.Tasks[2].Modules) +} + +// captureStdout redirects os.Stdout across the call so both renderers +// (renderPlanText writes to its *os.File arg; renderPlanJSON writes to +// os.Stdout directly) can be tested with the same helper. The renderer is +// invoked with os.Stdout as its arg during the redirect, so passing that +// pointer to renderPlanText makes both paths converge. +func (s *PlanRenderTestSuite) captureStdout(fn func(*os.File) error) string { + r, w, err := os.Pipe() + s.Require().NoError(err) + origStdout := os.Stdout + os.Stdout = w + defer func() { os.Stdout = origStdout }() + + fnErr := fn(w) + s.Require().NoError(w.Close()) + + buf, readErr := io.ReadAll(r) + s.Require().NoError(readErr) + s.Require().NoError(fnErr) + return string(buf) +} + +func TestPlanRenderSuite(t *testing.T) { + suite.Run(t, new(PlanRenderTestSuite)) +} + +// TestRenderPlanText_EmptyPlan covers the degenerate case: no modules and no +// tasks still produces well-formed headers instead of panicking. +func TestRenderPlanText_EmptyPlan(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + require.NoError(t, err) + + err = renderPlanText(runner.Plan{RepoRoot: "/x"}, w) + require.NoError(t, err) + require.NoError(t, w.Close()) + + buf, err := io.ReadAll(r) + require.NoError(t, err) + assert.Contains(t, string(buf), "Repository: /x") +} diff --git a/internal/cli/root.go b/internal/cli/root.go index 9e29ae6..36e83f0 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -65,7 +65,6 @@ and is equivalent to "prenup run". See the README for configuration.`, newInstallCmd(), newUninstallCmd(), newInitCmd(), - newMigrateCmd(), newConfigCmd(), newVersionCmd(), ) diff --git a/internal/cli/run_helpers_test.go b/internal/cli/run_helpers_test.go new file mode 100644 index 0000000..65ef445 --- /dev/null +++ b/internal/cli/run_helpers_test.go @@ -0,0 +1,207 @@ +package cli + +import ( + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +// TestDefaultRunOptions pins the zero value contract: the returned struct is +// the type's zero value, so any future field is opt-in by default. +func TestDefaultRunOptions(t *testing.T) { + t.Parallel() + assert.Equal(t, runOptions{}, defaultRunOptions()) +} + +// TestReadRunOptions round-trips every registered flag through the cobra +// command so a rename or type mismatch between addRunFlags and +// readRunOptions is caught. +func TestReadRunOptions(t *testing.T) { + t.Parallel() + + cmd := newRunCmd() + must := require.New(t) + + must.NoError(cmd.Flags().Set("config", "/tmp/x.yaml")) + must.NoError(cmd.Flags().Set("output", "json")) + must.NoError(cmd.Flags().Set("task", "lint")) + must.NoError(cmd.Flags().Set("task", "test")) + must.NoError(cmd.Flags().Set("all", "true")) + must.NoError(cmd.Flags().Set("no-interactive", "true")) + must.NoError(cmd.Flags().Set("no-clean-worktree", "true")) + must.NoError(cmd.Flags().Set("parallelism", "4")) + must.NoError(cmd.Flags().Set("dry-run", "true")) + + got := readRunOptions(cmd) + assert.Equal(t, runOptions{ + configPath: "/tmp/x.yaml", + outputMode: "json", + taskNames: []string{"lint", "test"}, + all: true, + noInteractive: true, + noCleanWorktree: true, + parallelism: 4, + dryRun: true, + }, got) +} + +// TestPrintlnSafe covers both branches: empty input is a no-op, non-empty +// input writes exactly one line. +func TestPrintlnSafe(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {name: "empty is no-op", in: "", want: ""}, + {name: "non-empty writes line", in: "hello", want: "hello\n"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := captureStdout(t, func() error { return printlnSafe(tc.in) }) + assert.Equal(t, tc.want, got) + }) + } +} + +// TestGithubTokenForVersionCheck exercises the env-var precedence: +// PRENUP_GITHUB_TOKEN wins over GITHUB_TOKEN wins over GH_TOKEN. +// Whitespace-only entries are treated as unset. +func TestGithubTokenForVersionCheck(t *testing.T) { + cases := []struct { + name string + env map[string]string + want string + }{ + {name: "prenup wins", env: map[string]string{"PRENUP_GITHUB_TOKEN": "p", "GITHUB_TOKEN": "g", "GH_TOKEN": "h"}, want: "p"}, + {name: "github beats gh", env: map[string]string{"GITHUB_TOKEN": "g", "GH_TOKEN": "h"}, want: "g"}, + {name: "gh only", env: map[string]string{"GH_TOKEN": "h"}, want: "h"}, + {name: "whitespace is unset", env: map[string]string{"PRENUP_GITHUB_TOKEN": " ", "GITHUB_TOKEN": "g"}, want: "g"}, + {name: "none set", env: nil, want: ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // t.Setenv restores on cleanup automatically; must-clear + // the other two keys so a leaking value in the process env + // (unlikely under `go test` but not impossible) can't + // influence the case. + for _, k := range []string{"PRENUP_GITHUB_TOKEN", "GITHUB_TOKEN", "GH_TOKEN"} { + t.Setenv(k, "") + } + for k, v := range tc.env { + t.Setenv(k, v) + } + assert.Equal(t, tc.want, githubTokenForVersionCheck()) + }) + } +} + +// TestResolvedVersionUsesInjectedValue confirms the -X ldflag path: if +// Version has been overridden at link time (or by tests), ResolvedVersion +// returns it. ResetVersionCacheForTest clears the sync.OnceValue so the +// swap actually takes effect. +func TestResolvedVersionUsesInjectedValue(t *testing.T) { + orig := Version + t.Cleanup(func() { + Version = orig + ResetVersionCacheForTest() + }) + + Version = "v9.8.7" + ResetVersionCacheForTest() + assert.Equal(t, "v9.8.7", ResolvedVersion()) +} + +// TestVersionCommandPrintsResolvedVersion covers newVersionCmd end-to-end +// through cobra's Execute so we exercise the RunE handler wiring too. +func TestVersionCommandPrintsResolvedVersion(t *testing.T) { + orig := Version + t.Cleanup(func() { + Version = orig + ResetVersionCacheForTest() + }) + Version = "v1.2.3-test" + ResetVersionCacheForTest() + + got := captureStdout(t, func() error { + return newVersionCmd().Execute() + }) + assert.Contains(t, got, "prenup v1.2.3-test") +} + +// AcquireRepoLockTestSuite exercises acquireRepoLock against a real +// initialized git repo. Two cases share the same fixture, so a suite pays +// its keep. +type AcquireRepoLockTestSuite struct { + suite.Suite + repo string +} + +func (s *AcquireRepoLockTestSuite) SetupTest() { + dir := s.T().TempDir() + cmd := exec.Command("git", "init", "-b", "main") + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git init: %s", string(out)) + s.repo = dir +} + +// TestDryRunSkipsLock verifies the documented dry-run behavior: no lock is +// acquired, so a subsequent lock file is not present. The release func must +// still be safe to call. +func (s *AcquireRepoLockTestSuite) TestDryRunSkipsLock() { + release, err := acquireRepoLock(s.repo, true) + s.Require().NoError(err) + s.Require().NotNil(release) + release() // must not panic + + // dry-run should not create the lock file. + _, err = os.Stat(filepath.Join(s.repo, ".git", "prenup.lock")) + s.True(os.IsNotExist(err), "dry-run should not create the lock file, but stat err was %v", err) +} + +// TestAcquireAndRelease exercises the happy path: acquire, release, and be +// able to acquire again. +func (s *AcquireRepoLockTestSuite) TestAcquireAndRelease() { + release, err := acquireRepoLock(s.repo, false) + s.Require().NoError(err) + s.Require().NotNil(release) + release() + + // After release, a second acquire should succeed. + release2, err := acquireRepoLock(s.repo, false) + s.Require().NoError(err) + release2() +} + +func TestAcquireRepoLockSuite(t *testing.T) { + suite.Run(t, new(AcquireRepoLockTestSuite)) +} + +// captureStdout redirects os.Stdout, invokes fn, and returns whatever fn +// wrote. Panics via t.Fatal on plumbing errors to keep call sites terse. +func captureStdout(t *testing.T, fn func() error) string { + t.Helper() + r, w, err := os.Pipe() + require.NoError(t, err) + orig := os.Stdout + os.Stdout = w + defer func() { os.Stdout = orig }() + + fnErr := fn() + require.NoError(t, w.Close()) + + buf, readErr := io.ReadAll(r) + require.NoError(t, readErr) + require.NoError(t, fnErr) + return strings.TrimSuffix(string(buf), "") +} diff --git a/internal/cli/run_internal_test.go b/internal/cli/run_internal_test.go index 92ba185..b73e01d 100644 --- a/internal/cli/run_internal_test.go +++ b/internal/cli/run_internal_test.go @@ -180,10 +180,10 @@ func TestLoadRunConfigExplicitPath(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "custom.yaml") - must.NoError(os.WriteFile(path, []byte("version: 2\ntasks:\n - name: t\n command: \"true\"\n"), 0o600)) + must.NoError(os.WriteFile(path, []byte("version: 1\ntasks:\n - name: t\n command: \"true\"\n"), 0o600)) cfg, err := loadRunConfig(dir, path) must.NoError(err) - is.Equal(2, cfg.Version) + is.Equal(1, cfg.Version) is.Len(cfg.Tasks, 1) } diff --git a/internal/config/config.go b/internal/config/config.go index 97191c7..22967e6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,8 @@ -// Package config defines the v2 .prenup.yaml schema, loading, defaults, and validation. +// Package config defines the .prenup.yaml schema, loading, defaults, and validation. package config import ( + "bytes" "errors" "fmt" "os" @@ -12,6 +13,12 @@ import ( "gopkg.in/yaml.v3" ) +// SchemaVersion is the config schema version this binary understands. It is a +// plain integer, independent of the prenup release version, and only +// increments on a breaking change to the config file format. Additive, +// non-breaking changes do not bump it. +const SchemaVersion = 1 + // ConfigFilenames lists the filenames prenup looks for in the repository root, // in order of preference. The first one found is used. var ConfigFilenames = []string{".prenup.yaml", ".prenup.yml"} @@ -30,7 +37,7 @@ const ( OutputJSON OutputMode = "json" ) -// Config is the parsed v2 configuration. +// Config is the parsed .prenup.yaml configuration. type Config struct { Version int `yaml:"version"` ModuleMarkers []string `yaml:"module_markers,omitempty"` @@ -83,7 +90,7 @@ func (t Task) ParallelEnabled() bool { func DefaultConfig() Config { trueVal := true return Config{ - Version: 2, + Version: SchemaVersion, ModuleMarkers: []string{"go.mod"}, CleanWorktree: &trueVal, MaxParallelism: 0, // 0 == NumCPU at runtime @@ -170,9 +177,7 @@ func Parse(data []byte, pathForErrors string) (Config, error) { } if cfg.Version == 0 { - // Missing `version:` is treated as v1 and rejected to force users - // through `prenup migrate`. - return Config{}, errors.New(`config is missing "version: 2". If this is a v1 config, run "prenup migrate"`) + return Config{}, fmt.Errorf(`config is missing "version: %d"`, SchemaVersion) } // Apply defaults for fields YAML unmarshal would leave at zero. @@ -210,8 +215,15 @@ func Validate(cfg Config) error { // validateTopLevel checks all non-task fields. func validateTopLevel(cfg Config) []string { var errs []string - if cfg.Version != 2 { - errs = append(errs, fmt.Sprintf("unsupported config version %d (expected 2)", cfg.Version)) + switch { + case cfg.Version == SchemaVersion: + // supported + case cfg.Version > SchemaVersion: + errs = append(errs, fmt.Sprintf( + "config version %d requires a newer version of prenup (this binary supports version %d); please upgrade prenup", + cfg.Version, SchemaVersion)) + default: + errs = append(errs, fmt.Sprintf("unsupported config version %d (expected %d)", cfg.Version, SchemaVersion)) } switch cfg.Output { case OutputAuto, OutputHuman, OutputMarkdown, OutputJSON, "": @@ -294,7 +306,32 @@ func versionTypeHint(data []byte) string { return "" } if probe.Version.Kind == yaml.ScalarNode && probe.Version.Tag == "!!str" { - return `"version" must be an integer (use "version: 2", not "version: \"2\"")` + return `"version" must be an integer (use "version: 1", not "version: \"1\"")` } return "" } + +// Marshal writes a config back to YAML with a stable layout, led by a +// discoverability header so anyone who encounters a .prenup.yaml can find out +// what it is. Any code path that rewrites a config must go through Marshal so +// the header is preserved. +func Marshal(cfg Config) ([]byte, error) { + var buf bytes.Buffer + buf.WriteString(configHeader) + enc := yaml.NewEncoder(&buf) + enc.SetIndent(2) + if err := enc.Encode(cfg); err != nil { + return nil, fmt.Errorf("encoding config: %w", err) + } + if err := enc.Close(); err != nil { + return nil, fmt.Errorf("closing encoder: %w", err) + } + return buf.Bytes(), nil +} + +// configHeader is the comment block prepended to generated .prenup.yaml files. +const configHeader = `# .prenup.yaml — pre-commit checks managed by prenup. +# What is this? Prenup runs configurable checks before each commit, scoped to +# the parts of the repo that changed. Learn more and install: +# https://github.com/c2fo/prenup +` diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 27752fb..b39964b 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -11,7 +11,7 @@ import ( func TestParseValid(t *testing.T) { data := []byte(` -version: 2 +version: 1 module_markers: - go.mod exclude: @@ -33,7 +33,7 @@ tasks: cfg, err := Parse(data, "/tmp/test.yaml") require.NoError(t, err) - assert.Equal(t, 2, cfg.Version) + assert.Equal(t, 1, cfg.Version) assert.Equal(t, []string{"go.mod"}, cfg.ModuleMarkers) assert.Equal(t, 4, cfg.MaxParallelism) assert.Equal(t, OutputJSON, cfg.Output) @@ -56,12 +56,36 @@ tasks: `) _, err := Parse(data, "/tmp/test.yaml") require.Error(t, err) - assert.Contains(t, err.Error(), `missing "version: 2"`) + assert.Contains(t, err.Error(), `missing "version: 1"`) } -func TestValidateRejectsDuplicateTaskName(t *testing.T) { +func TestParseNewerVersionRejected(t *testing.T) { data := []byte(` version: 2 +tasks: + - name: foo + command: "true" +`) + _, err := Parse(data, "/tmp/test.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), "requires a newer version of prenup") +} + +func TestParseUnsupportedOlderVersionRejected(t *testing.T) { + data := []byte(` +version: -1 +tasks: + - name: foo + command: "true" +`) + _, err := Parse(data, "/tmp/test.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported config version") +} + +func TestValidateRejectsDuplicateTaskName(t *testing.T) { + data := []byte(` +version: 1 tasks: - name: foo command: "true" @@ -75,7 +99,7 @@ tasks: func TestValidateRequiresOutputPatternsForStageOutput(t *testing.T) { data := []byte(` -version: 2 +version: 1 tasks: - name: foo command: "true" @@ -88,7 +112,7 @@ tasks: func TestValidateRejectsUnknownOutputMode(t *testing.T) { data := []byte(` -version: 2 +version: 1 output: flashy tasks: - name: foo @@ -101,8 +125,8 @@ tasks: func TestFindPrefersYamlOverYml(t *testing.T) { dir := t.TempDir() - require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte("version: 2\n"), 0o600)) - require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yml"), []byte("version: 2\n"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yaml"), []byte("version: 1\n"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".prenup.yml"), []byte("version: 1\n"), 0o600)) p, err := Find(dir) require.NoError(t, err) @@ -113,7 +137,7 @@ func TestLoadRejectsPathOutsideRepo(t *testing.T) { repo := t.TempDir() other := t.TempDir() cfgPath := filepath.Join(other, ".prenup.yaml") - require.NoError(t, os.WriteFile(cfgPath, []byte("version: 2\ntasks: [{name: f, command: \"true\"}]\n"), 0o600)) + require.NoError(t, os.WriteFile(cfgPath, []byte("version: 1\ntasks: [{name: f, command: \"true\"}]\n"), 0o600)) _, err := Load(cfgPath, repo) require.Error(t, err) @@ -129,7 +153,7 @@ func TestLoadRejectsSymlinkEscapingRepo(t *testing.T) { other := t.TempDir() target := filepath.Join(other, ".prenup.yaml") - require.NoError(t, os.WriteFile(target, []byte("version: 2\ntasks: [{name: f, command: \"true\"}]\n"), 0o600)) + require.NoError(t, os.WriteFile(target, []byte("version: 1\ntasks: [{name: f, command: \"true\"}]\n"), 0o600)) link := filepath.Join(repo, ".prenup.yaml") if err := os.Symlink(target, link); err != nil { diff --git a/internal/config/marshal_test.go b/internal/config/marshal_test.go new file mode 100644 index 0000000..8f0b6ad --- /dev/null +++ b/internal/config/marshal_test.go @@ -0,0 +1,105 @@ +package config + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestMarshalRoundTrip verifies that a config emitted by Marshal parses back +// cleanly, is self-consistent, and always leads with the discoverability +// header. This pins the invariant that any config prenup writes advertises +// the project. +func TestMarshalRoundTrip(t *testing.T) { + t.Parallel() + + cfg := DefaultConfig() + cfg.CleanWorktree = nil // let it inherit defaults on reload + cfg.Tasks = []Task{ + {Name: "test", Command: "go test ./...", DefaultSelected: true, PerModule: true}, + } + + out, err := Marshal(cfg) + require.NoError(t, err) + + got := string(out) + assert.True(t, strings.HasPrefix(got, "# .prenup.yaml"), + "marshal output must lead with the discoverability header, got: %q", got[:min(len(got), 40)]) + assert.Contains(t, got, "https://github.com/c2fo/prenup", + "header must advertise the project URL") + assert.Contains(t, got, "version: 1") + assert.Contains(t, got, "go test ./...") + + reloaded, err := Parse(out, "/tmp/out.yaml") + require.NoError(t, err) + assert.Equal(t, cfg.Tasks[0].Name, reloaded.Tasks[0].Name) + assert.Equal(t, cfg.Tasks[0].Command, reloaded.Tasks[0].Command) + assert.True(t, reloaded.CleanWorktreeEnabled(), "nil CleanWorktree should default to true on reload") +} + +// TestConfigCleanWorktreeEnabled exercises both branches of the +// Config-level default. +func TestConfigCleanWorktreeEnabled(t *testing.T) { + t.Parallel() + + tr, fl := true, false + cases := []struct { + name string + cfg Config + want bool + }{ + {name: "nil defaults to true", cfg: Config{}, want: true}, + {name: "explicit true", cfg: Config{CleanWorktree: &tr}, want: true}, + {name: "explicit false", cfg: Config{CleanWorktree: &fl}, want: false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, tc.cfg.CleanWorktreeEnabled()) + }) + } +} + +// TestTaskCleanWorktreeEnabled exercises the Task-level override: +// a nil pointer inherits the config default, a set pointer wins. +func TestTaskCleanWorktreeEnabled(t *testing.T) { + t.Parallel() + + tr, fl := true, false + cases := []struct { + name string + task Task + cfgDefault bool + want bool + }{ + {name: "nil inherits cfg default (true)", task: Task{}, cfgDefault: true, want: true}, + {name: "nil inherits cfg default (false)", task: Task{}, cfgDefault: false, want: false}, + {name: "explicit true overrides false", task: Task{CleanWorktree: &tr}, cfgDefault: false, want: true}, + {name: "explicit false overrides true", task: Task{CleanWorktree: &fl}, cfgDefault: true, want: false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, tc.task.CleanWorktreeEnabled(tc.cfgDefault)) + }) + } +} + +// TestVersionTypeHint fires when a user writes `version: "1"` (quoted +// string) instead of the integer form. The hint should call out the +// specific footgun to save round-trips through the YAML unmarshaler. +func TestVersionTypeHint(t *testing.T) { + t.Parallel() + + quoted := []byte(` +version: "1" +tasks: + - name: t + command: "true" +`) + _, err := Parse(quoted, "/tmp/quoted.yaml") + require.Error(t, err) + assert.Contains(t, err.Error(), `"version" must be an integer`) +} diff --git a/internal/config/migrate.go b/internal/config/migrate.go deleted file mode 100644 index 952cc15..0000000 --- a/internal/config/migrate.go +++ /dev/null @@ -1,76 +0,0 @@ -package config - -import ( - "bytes" - "fmt" - - "gopkg.in/yaml.v3" -) - -// V1Config matches the v1 YAML shape so we can read existing .prenup.yml files -// and rewrite them as v2. -type V1Config struct { - Exclude []string `yaml:"exclude"` - Tasks []V1Task `yaml:"tasks"` -} - -// V1Task is the v1 task entry. -type V1Task struct { - Name string `yaml:"name"` - DefaultSelected bool `yaml:"default_selected"` - PerModule bool `yaml:"per_module"` - Command string `yaml:"command,omitempty"` - WorkingDir string `yaml:"working_dir,omitempty"` - StageOutput bool `yaml:"stage_output,omitempty"` - OutputPatterns []string `yaml:"output_patterns,omitempty"` -} - -// MigrateV1 converts v1 YAML bytes into a v2 Config and returns freshly -// marshaled YAML bytes. The returned Config is also populated for callers that -// want to inspect it directly. -func MigrateV1(data []byte) (Config, []byte, error) { - var v1 V1Config - if err := yaml.Unmarshal(data, &v1); err != nil { - return Config{}, nil, fmt.Errorf("parsing v1 config: %w", err) - } - - cfg := DefaultConfig() - cfg.Exclude = v1.Exclude - cfg.Tasks = make([]Task, 0, len(v1.Tasks)) - for _, t := range v1.Tasks { - cfg.Tasks = append(cfg.Tasks, Task{ - Name: t.Name, - DefaultSelected: t.DefaultSelected, - Command: t.Command, - PerModule: t.PerModule, - WorkingDir: t.WorkingDir, - OutputPatterns: t.OutputPatterns, - StageOutput: t.StageOutput, - }) - } - - // Omit the default CleanWorktree pointer from the marshaled output unless - // the user has opinions; defaults are applied at load time. - cfg.CleanWorktree = nil - - out, err := Marshal(cfg) - if err != nil { - return Config{}, nil, err - } - return cfg, out, nil -} - -// Marshal writes a v2 config back to YAML with a stable, commented layout. -func Marshal(cfg Config) ([]byte, error) { - var buf bytes.Buffer - buf.WriteString("# Prenup configuration. See https://github.com/c2fo/prenup for docs.\n") - enc := yaml.NewEncoder(&buf) - enc.SetIndent(2) - if err := enc.Encode(cfg); err != nil { - return nil, fmt.Errorf("encoding config: %w", err) - } - if err := enc.Close(); err != nil { - return nil, fmt.Errorf("closing encoder: %w", err) - } - return buf.Bytes(), nil -} diff --git a/internal/config/migrate_test.go b/internal/config/migrate_test.go deleted file mode 100644 index 9d274cd..0000000 --- a/internal/config/migrate_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package config - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// v1 example taken from the pre-v2 prenup.example.yaml. -const v1ConfigExample = ` -exclude: - - ".github" - - "*.yaml" - -tasks: - - name: "Run tests" - default_selected: true - command: "go test ./..." - per_module: true - - - name: "Generate CLI docs" - default_selected: true - per_module: true - command: "make docs" - working_dir: "{{.repo_root}}" - output_patterns: - - "doc/cmd/*.md" - stage_output: true -` - -func TestMigrateV1Preserves(t *testing.T) { - cfg, out, err := MigrateV1([]byte(v1ConfigExample)) - require.NoError(t, err) - - assert.Equal(t, 2, cfg.Version) - require.Len(t, cfg.Tasks, 2) - assert.Equal(t, "Run tests", cfg.Tasks[0].Name) - assert.True(t, cfg.Tasks[0].PerModule) - assert.True(t, cfg.Tasks[1].StageOutput) - - assert.True(t, strings.HasPrefix(string(out), "# Prenup configuration.")) - assert.Contains(t, string(out), "version: 2") - assert.Contains(t, string(out), "Run tests") -} - -func TestMigrateThenParseRoundTrips(t *testing.T) { - _, out, err := MigrateV1([]byte(v1ConfigExample)) - require.NoError(t, err) - - reparsed, err := Parse(out, "migrated") - require.NoError(t, err) - require.Len(t, reparsed.Tasks, 2) - assert.Equal(t, "Run tests", reparsed.Tasks[0].Name) -} diff --git a/internal/config/schema.go b/internal/config/schema.go index 8fd06fc..7c778b0 100644 --- a/internal/config/schema.go +++ b/internal/config/schema.go @@ -5,7 +5,7 @@ import _ "embed" //go:embed schema.json var embeddedSchema []byte -// Schema returns the embedded JSON schema describing the v2 config. +// Schema returns the embedded JSON schema describing the config. // The bytes are stable across calls; callers must not mutate them. func Schema() []byte { return embeddedSchema diff --git a/internal/config/schema.json b/internal/config/schema.json index 31ef3a1..f1d6e96 100644 --- a/internal/config/schema.json +++ b/internal/config/schema.json @@ -1,16 +1,16 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/c2fo/prenup/main/assets/prenup.schema.json", - "title": "Prenup v2 configuration", - "$comment": "This file is the canonical source. assets/prenup.schema.json must stay byte-identical. When adding a v3, replace `version.const: 2` with a `oneOf` over per-version schemas.", + "title": "Prenup configuration", + "$comment": "This file is the canonical source. assets/prenup.schema.json must stay byte-identical. When adding a version 2, replace `version.const: 1` with a `oneOf` over per-version schemas.", "type": "object", "required": ["version", "tasks"], "additionalProperties": false, "properties": { "version": { "type": "integer", - "const": 2, - "description": "Config schema version. Must be 2." + "const": 1, + "description": "Config schema version. Must be 1." }, "module_markers": { "type": "array", diff --git a/internal/discover/changes_test.go b/internal/discover/changes_test.go new file mode 100644 index 0000000..22170b3 --- /dev/null +++ b/internal/discover/changes_test.go @@ -0,0 +1,115 @@ +package discover + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/c2fo/prenup/internal/git" +) + +// TestValidatePatterns is a truth table for the pattern validator. Errors +// only bubble up for malformed doublestar syntax; unmatched-but-valid +// patterns are fine. +func TestValidatePatterns(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + patterns []string + wantErr string + }{ + {name: "nil", patterns: nil}, + {name: "empty", patterns: []string{}}, + {name: "valid patterns", patterns: []string{"**/*.go", "vendor/**", "!*.md"}}, + {name: "malformed bracket", patterns: []string{"[unclosed"}, wantErr: "invalid pattern"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := ValidatePatterns(tc.patterns) + if tc.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + return + } + assert.NoError(t, err) + }) + } +} + +// ChangedFilesTestSuite exercises ChangedFiles against a real git repo. +// The three cases share a non-trivial setup (git init + seed commit), so a +// suite pays for itself. +type ChangedFilesTestSuite struct { + suite.Suite + repo string + runner *git.Runner +} + +func (s *ChangedFilesTestSuite) SetupTest() { + dir := s.T().TempDir() + for _, args := range [][]string{ + {"init", "-b", "main"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "test"}, + {"config", "commit.gpgsign", "false"}, + } { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed binary, internal args. + cmd.Dir = dir + out, err := cmd.CombinedOutput() + s.Require().NoErrorf(err, "git %s: %s", strings.Join(args, " "), string(out)) + } + s.Require().NoError(os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi\n"), 0o600)) + for _, args := range [][]string{{"add", "README.md"}, {"commit", "-m", "init"}} { + cmd := exec.Command("git", args...) //nolint:gosec // G204: fixed binary, internal args. + cmd.Dir = dir + s.Require().NoError(cmd.Run()) + } + s.repo = dir + s.runner = git.New(dir) +} + +// TestReturnsUntrackedAndStaged: an untracked file plus a staged file both +// appear, deduplicated. +func (s *ChangedFilesTestSuite) TestReturnsUntrackedAndStaged() { + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "a.go"), []byte("package a\n"), 0o600)) + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "b.go"), []byte("package b\n"), 0o600)) + cmd := exec.Command("git", "add", "a.go") + cmd.Dir = s.repo + s.Require().NoError(cmd.Run()) + + got, err := ChangedFiles(s.runner, nil) + s.Require().NoError(err) + s.ElementsMatch([]string{"a.go", "b.go"}, got) +} + +// TestExcludePatternDropsFiles: exclude patterns filter out matching paths. +func (s *ChangedFilesTestSuite) TestExcludePatternDropsFiles() { + s.Require().NoError(os.MkdirAll(filepath.Join(s.repo, "vendor"), 0o750)) + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "vendor", "dep.go"), []byte("package v\n"), 0o600)) + s.Require().NoError(os.WriteFile(filepath.Join(s.repo, "keep.go"), []byte("package k\n"), 0o600)) + + got, err := ChangedFiles(s.runner, []string{"vendor/**"}) + s.Require().NoError(err) + s.NotContains(got, "vendor/dep.go", "vendor/** should be excluded") + s.Contains(got, "keep.go") +} + +// TestEmptyRepoReturnsEmpty: a clean worktree returns an empty slice, no error. +func (s *ChangedFilesTestSuite) TestEmptyRepoReturnsEmpty() { + got, err := ChangedFiles(s.runner, nil) + s.Require().NoError(err) + s.Empty(got) +} + +func TestChangedFilesSuite(t *testing.T) { + suite.Run(t, new(ChangedFilesTestSuite)) +} diff --git a/internal/git/version_test.go b/internal/git/version_test.go new file mode 100644 index 0000000..40adbe4 --- /dev/null +++ b/internal/git/version_test.go @@ -0,0 +1,24 @@ +package git + +import ( + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestVersion pins the diagnostic contract of Runner.Version: it must +// return a non-empty string that starts with "git version" (the git CLI +// output format). Skipped if the git binary isn't on PATH so the test +// still passes in minimal build environments. +func TestVersion(t *testing.T) { + t.Parallel() + if _, err := exec.LookPath("git"); err != nil { + t.Skip("git not available on this platform") + } + + got, err := New("").Version() + require.NoError(t, err) + assert.Contains(t, got, "git version") +} diff --git a/internal/hook/exists_error_test.go b/internal/hook/exists_error_test.go new file mode 100644 index 0000000..9fc256a --- /dev/null +++ b/internal/hook/exists_error_test.go @@ -0,0 +1,22 @@ +package hook + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestExistsError_Error pins the user-facing message. This is the string a +// user sees when a competing pre-commit hook blocks install; changing the +// wording would break the CLI's `--force`/`--replace`/`--chain` guidance. +func TestExistsError_Error(t *testing.T) { + t.Parallel() + + err := &ExistsError{Path: "/repo/.git/hooks/pre-commit"} + msg := err.Error() + + assert.Contains(t, msg, "/repo/.git/hooks/pre-commit") + assert.Contains(t, msg, "--force") + assert.Contains(t, msg, "--replace") + assert.Contains(t, msg, "--chain") +} diff --git a/internal/lock/helpers_test.go b/internal/lock/helpers_test.go new file mode 100644 index 0000000..88ceb84 --- /dev/null +++ b/internal/lock/helpers_test.go @@ -0,0 +1,99 @@ +package lock + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSplitLines is a truth table for the internal newline splitter. It +// intentionally keeps blank lines (unlike git.splitLines) so callers can +// preserve blank-line semantics if they want; it just splits on '\n'. +func TestSplitLines(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + in string + want []string + }{ + {name: "empty", in: "", want: nil}, + {name: "single line no newline", in: "hello", want: []string{"hello"}}, + {name: "single line with newline", in: "hello\n", want: []string{"hello"}}, + {name: "two lines", in: "a\nb", want: []string{"a", "b"}}, + {name: "trailing blank line preserved", in: "a\n\n", want: []string{"a", ""}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, splitLines(tc.in)) + }) + } +} + +// TestTrimSpaceAndIsSpace covers both helpers together since trimSpace's +// only interesting behavior is that it delegates to isSpace. +func TestTrimSpaceAndIsSpace(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + in string + want string + }{ + {name: "no whitespace", in: "abc", want: "abc"}, + {name: "leading and trailing spaces", in: " abc ", want: "abc"}, + {name: "tabs and CR", in: "\t\rabc\t\r", want: "abc"}, + {name: "all whitespace", in: " \t\n\r ", want: ""}, + {name: "empty", in: "", want: ""}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, trimSpace(tc.in)) + }) + } + + assert.True(t, isSpace(' ')) + assert.True(t, isSpace('\t')) + assert.True(t, isSpace('\r')) + assert.True(t, isSpace('\n')) + assert.False(t, isSpace('a')) + assert.False(t, isSpace(0)) +} + +// TestResolveGitFile covers the worktree/submodule support: a .git file +// (regular file, not directory) pointing to a real gitdir should resolve. +func TestResolveGitFile(t *testing.T) { + t.Parallel() + + t.Run("returns false when file is missing", func(t *testing.T) { + t.Parallel() + _, ok := resolveGitFile(filepath.Join(t.TempDir(), "nope")) + assert.False(t, ok) + }) + + t.Run("returns false when prefix is missing", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + f := filepath.Join(dir, ".git") + require.NoError(t, os.WriteFile(f, []byte("something else\n"), 0o600)) + _, ok := resolveGitFile(f) + assert.False(t, ok) + }) + + t.Run("returns trimmed gitdir when present", func(t *testing.T) { + t.Parallel() + dir := t.TempDir() + f := filepath.Join(dir, ".git") + require.NoError(t, os.WriteFile(f, []byte("gitdir: /some/where\n"), 0o600)) + got, ok := resolveGitFile(f) + require.True(t, ok) + assert.Equal(t, "/some/where", got) + }) +} diff --git a/internal/runner/exec_test.go b/internal/runner/exec_test.go new file mode 100644 index 0000000..13bf478 --- /dev/null +++ b/internal/runner/exec_test.go @@ -0,0 +1,133 @@ +package runner + +import ( + "context" + "os/exec" + "sort" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestFlattenEnv verifies the deterministic-shape contract: every entry is +// K=V and the set is exactly the input map. We sort before comparing since +// map iteration order isn't stable. +func TestFlattenEnv(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + in map[string]string + want []string + }{ + {name: "empty map", in: map[string]string{}, want: []string{}}, + {name: "nil map", in: nil, want: []string{}}, + {name: "single entry", in: map[string]string{"X": "1"}, want: []string{"X=1"}}, + { + name: "multiple entries deterministic", + in: map[string]string{"A": "1", "B": "2", "C": "3"}, + want: []string{"A=1", "B=2", "C=3"}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := flattenEnv(tc.in) + sort.Strings(got) + sort.Strings(tc.want) + assert.Equal(t, tc.want, got) + }) + } +} + +// TestBashExecutor_Run_HappyPath exercises the full pipe/pump/wait flow: a +// short bash command emits stdout and stderr lines, both are streamed to +// onLine with the correct Stream, and Run returns nil on exit 0. +func TestBashExecutor_Run_HappyPath(t *testing.T) { + t.Parallel() + if _, err := exec.LookPath("bash"); err != nil { + t.Skip("bash not available on this platform") + } + + var ( + mu sync.Mutex + lines []struct { + stream Stream + text string + } + ) + onLine := func(s Stream, txt string) { + mu.Lock() + defer mu.Unlock() + lines = append(lines, struct { + stream Stream + text string + }{s, txt}) + } + + err := BashExecutor{}.Run(context.Background(), + `echo hello; echo boom 1>&2`, "", map[string]string{"PRENUP_TEST": "x"}, onLine) + require.NoError(t, err) + + mu.Lock() + defer mu.Unlock() + require.Len(t, lines, 2) + + // Order between stdout and stderr isn't guaranteed (separate goroutines), + // so sort by text before asserting. + sort.Slice(lines, func(i, j int) bool { return lines[i].text < lines[j].text }) + assert.Equal(t, StreamStderr, lines[0].stream) + assert.Equal(t, "boom", lines[0].text) + assert.Equal(t, StreamStdout, lines[1].stream) + assert.Equal(t, "hello", lines[1].text) +} + +// TestBashExecutor_Run_NonZeroExit surfaces the exit status as an error +// prefixed with "exit status N". +func TestBashExecutor_Run_NonZeroExit(t *testing.T) { + t.Parallel() + if _, err := exec.LookPath("bash"); err != nil { + t.Skip("bash not available on this platform") + } + + err := BashExecutor{}.Run(context.Background(), "exit 7", "", nil, func(Stream, string) {}) + require.Error(t, err) + assert.Contains(t, err.Error(), "exit status 7") +} + +// TestBashExecutor_Run_ContextCancel_ReturnsPromptly asserts that canceling +// the parent context terminates a long-running command within the grace +// window instead of waiting on it forever. The Cancel/WaitDelay wiring is +// subtle enough that a regression here would show up as a hang under load. +func TestBashExecutor_Run_ContextCancel_ReturnsPromptly(t *testing.T) { + t.Parallel() + if _, err := exec.LookPath("bash"); err != nil { + t.Skip("bash not available on this platform") + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(50 * time.Millisecond) + cancel() + }() + + start := time.Now() + err := BashExecutor{GracefulShutdown: 200 * time.Millisecond}. + Run(ctx, "sleep 30", "", nil, func(Stream, string) {}) + elapsed := time.Since(start) + + require.Error(t, err, "canceled command should return an error") + assert.Less(t, elapsed, 5*time.Second, "cancel path must not hang for the full sleep") +} + +// TestDiscardSink covers the trivial no-op sink's contract. +func TestDiscardSink(t *testing.T) { + t.Parallel() + var s DiscardSink + assert.NotPanics(t, func() { s.Emit(Event{Kind: EventRunStarted}) }) + assert.NoError(t, s.Close()) +} diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 0ece969..6b69430 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -105,7 +105,7 @@ func (l *atomicCallLog) len() int { func (s *RunnerTestSuite) TestSequentialSuccess() { cfg := config.Config{ - Version: 2, + Version: 1, ModuleMarkers: []string{"go.mod"}, Tasks: []config.Task{ {Name: "A", Command: "cmd-a", DefaultSelected: true, PerModule: true}, @@ -147,7 +147,7 @@ func (s *RunnerTestSuite) TestSequentialSuccess() { // to emit. func (s *RunnerTestSuite) TestFailFastWithinTaskAcrossModules() { cfg := config.Config{ - Version: 2, + Version: 1, ModuleMarkers: []string{"go.mod"}, Tasks: []config.Task{ {Name: "Lint", Command: "lint", DefaultSelected: true, PerModule: true}, @@ -185,7 +185,7 @@ func (s *RunnerTestSuite) TestFailFastWithinTaskAcrossModules() { func (s *RunnerTestSuite) TestBuildPlanAppliesPathFilter() { cfg := config.Config{ - Version: 2, + Version: 1, ModuleMarkers: []string{"go.mod"}, Tasks: []config.Task{ { @@ -206,7 +206,7 @@ func (s *RunnerTestSuite) TestBuildPlanAppliesPathFilter() { func (s *RunnerTestSuite) TestBuildPlanSelectionMap() { cfg := config.Config{ - Version: 2, + Version: 1, ModuleMarkers: []string{"go.mod"}, Tasks: []config.Task{ {Name: "A", Command: "a", DefaultSelected: true, PerModule: true}, diff --git a/internal/ui/agent_test.go b/internal/ui/agent_test.go new file mode 100644 index 0000000..3926afe --- /dev/null +++ b/internal/ui/agent_test.go @@ -0,0 +1,50 @@ +package ui + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestVersionLabel is a truth table for the human-readable version formatter. +// The three branches (unknown / dev build / release) all show up verbatim in +// user-facing output and in the agent_hint event, so a wording regression +// here would leak into external contracts. +func TestVersionLabel(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + version string + want string + }{ + {name: "empty is unknown", version: "", want: "unknown"}, + {name: "dev gets development-build suffix", version: "dev", want: "dev (development build, not a tagged release)"}, + {name: "release passes through", version: "v1.2.3", want: "v1.2.3"}, + {name: "arbitrary token passes through", version: "abc123", want: "abc123"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + assert.Equal(t, tc.want, VersionLabel(tc.version)) + }) + } +} + +// TestAgentConstantsExposeStableStrings pins the exported strings that +// consumers may pattern-match against. These aren't secrets or long +// paragraphs of copy, but they are stable API surface as far as JSON +// consumers are concerned. +func TestAgentConstantsExposeStableStrings(t *testing.T) { + t.Parallel() + + assert.Equal(t, "prenup", Tool) + assert.Equal(t, "https://github.com/c2fo/prenup", HomepageURL) + assert.Equal(t, "dev", DevVersion) + assert.Equal(t, "1", AgentHintSchema) + assert.NotEmpty(t, Description) + assert.NotEmpty(t, HookContextNote) + assert.NotEmpty(t, JSONHint) + assert.NotEmpty(t, FailureBypassHint) +} diff --git a/prenup.example.yaml b/prenup.example.yaml index 8759c62..8ed84b0 100644 --- a/prenup.example.yaml +++ b/prenup.example.yaml @@ -1,4 +1,7 @@ -# Prenup pre-commit hook configuration. +# .prenup.yaml — pre-commit checks managed by prenup. +# What is this? Prenup runs configurable checks before each commit, scoped to +# the parts of the repo that changed. Learn more and install: +# https://github.com/c2fo/prenup # # Place this file at the root of your git repository as `.prenup.yaml` (or # `.prenup.yml`). Run `prenup install` once per repo to wire up the hook. @@ -7,7 +10,7 @@ # reference and https://github.com/c2fo/prenup/blob/main/assets/prenup.schema.json # for an editor-friendly JSON schema. -version: 2 +version: 1 # Files whose presence marks a "module". Defaults to [go.mod]. Configurable so # polyglot repos can opt in to other languages. From df4b7ff38b6028ca87f18a77bda7aab019688b24 Mon Sep 17 00:00:00 2001 From: John Judd Date: Sat, 4 Jul 2026 17:33:35 -0500 Subject: [PATCH 5/7] fix: address PR #1 review comments Resolves the eight Copilot review comments on PR #1: - internal/cli/config_cmd_test.go: assert against the actual `config validate` output shape (path, version, task count) instead of the task name, which the handler intentionally does not echo. - internal/cli/plan.go: `--output` help no longer advertises unsupported `auto`/`human`/`markdown` modes; only `text` (default) and `json` are implemented. - docs/SCHEMA.md: clarify that the `time` field is present on every runner event but omitted from the bootstrap `agent_hint` line, which is a static self-describing header rather than a timestamped event. - internal/versioncheck/versioncheck.go: request the GitHub Releases API with `?per_page=100` so repos accumulating many releases (or non- semver tags) can't push the latest valid semver release off the first page. We deliberately do not paginate on this pre-commit hot path. - internal/versioncheck/versioncheck.go: render the `X-RateLimit-Reset` header as an RFC3339 UTC time plus a "resets in ", falling back to "reset time unavailable" on parse failure, instead of leaking the raw Unix epoch integer to operators. New TestFormatRateLimitReset covers the fallback branches; the existing rate-limit test asserts against the new format. - internal/ui/agent.go, internal/ui/jsonout/jsonout.go: fix three `prenup/docs/SCHEMA.md` references (including the agent-facing `event_types_note` hint string) to the correct in-repo path `docs/SCHEMA.md`. --- CHANGELOG.md | 18 +++++++++++ docs/SCHEMA.md | 2 +- internal/cli/config_cmd_test.go | 7 ++++- internal/cli/plan.go | 2 +- internal/ui/agent.go | 2 +- internal/ui/jsonout/jsonout.go | 4 +-- internal/versioncheck/versioncheck.go | 36 +++++++++++++++++----- internal/versioncheck/versioncheck_test.go | 30 ++++++++++++++++-- 8 files changed, 86 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c765d..3012b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- `prenup plan --output` help text no longer advertises unsupported + `auto`/`human`/`markdown` modes; only `text` (default) and `json` are + implemented. +- `docs/SCHEMA.md` no longer claims the `time` field is present on every + NDJSON line — the bootstrap `agent_hint` line is a static header and + intentionally omits it. +- Version checker requests the GitHub Releases API with `?per_page=100` + so repos accumulating many releases (or non-semver tags) don't cause + the latest valid semver release to fall off the first page. +- Rate-limit exhaustion errors now render the `X-RateLimit-Reset` header + as an RFC3339 UTC time plus a "resets in" duration, instead of leaking + the raw Unix epoch integer to the operator. +- Internal comments in `internal/ui/agent.go` and + `internal/ui/jsonout/jsonout.go` (including the agent-facing + `event_types_note` hint) now point at `docs/SCHEMA.md`, matching the + actual in-repo path. + ### Added - Initial open-source release. - Interactive, configuration-driven Git pre-commit hook runner. diff --git a/docs/SCHEMA.md b/docs/SCHEMA.md index a35f331..f35b7b0 100644 --- a/docs/SCHEMA.md +++ b/docs/SCHEMA.md @@ -62,7 +62,7 @@ pretty-printed JSON document. See "Plan output" below. | Field | Type | Notes | |---|---|---| | `type` | string | Event kind (see below). | -| `time` | RFC3339 timestamp | Always present. | +| `time` | RFC3339 timestamp | Present on every runner event. Omitted from the bootstrap `agent_hint` line, which is a static self-describing header rather than a timestamped event. | ### Event kinds diff --git a/internal/cli/config_cmd_test.go b/internal/cli/config_cmd_test.go index 7bfba30..f8c081a 100644 --- a/internal/cli/config_cmd_test.go +++ b/internal/cli/config_cmd_test.go @@ -70,8 +70,13 @@ tasks: out := captureStdout(s.T(), func() error { return newConfigValidateCmd().RunE(nil, []string{explicit}) }) + // `config validate` prints `OK: parses cleanly (v, tasks)`; + // assert against those fields (the task *name* is intentionally not + // echoed, so don't grep for it). s.Contains(out, "OK:") - s.Contains(out, "Explicit") + s.Contains(out, explicit) + s.Contains(out, "v1,") + s.Contains(out, "1 tasks") } // TestValidate_MissingConfig surfaces a helpful error when there is no diff --git a/internal/cli/plan.go b/internal/cli/plan.go index 63dd99f..4333016 100644 --- a/internal/cli/plan.go +++ b/internal/cli/plan.go @@ -26,7 +26,7 @@ func newPlanCmd() *cobra.Command { }, } cmd.Flags().String("config", "", "path to .prenup.yaml") - cmd.Flags().String("output", "", "output mode: auto, human, markdown, or json") + cmd.Flags().String("output", "", "output mode: text (default) or json") cmd.Flags().Bool("all", false, "ignore change detection") return cmd } diff --git a/internal/ui/agent.go b/internal/ui/agent.go index 5987ae5..3f28d49 100644 --- a/internal/ui/agent.go +++ b/internal/ui/agent.go @@ -7,7 +7,7 @@ package ui // // When you change one of these constants, please also: // - re-run `go test ./internal/ui/...` (golden assertions key off them) -// - update prenup/docs/SCHEMA.md if you change AgentHintSchema or the +// - update docs/SCHEMA.md if you change AgentHintSchema or the // shape of the agent_hint event. // Tool is the project name as it should appear to consumers. Stable. diff --git a/internal/ui/jsonout/jsonout.go b/internal/ui/jsonout/jsonout.go index ffd9e9b..cf9783c 100644 --- a/internal/ui/jsonout/jsonout.go +++ b/internal/ui/jsonout/jsonout.go @@ -1,5 +1,5 @@ // Package jsonout is a runner.Sink that writes one JSON object per line. -// The schema is documented in prenup/docs/SCHEMA.md and is intended to be +// The schema is documented in docs/SCHEMA.md and is intended to be // consumed by agents and scripting clients. package jsonout @@ -56,7 +56,7 @@ func New(w io.Writer) *Sink { StreamFormat: "ndjson", EventTypesNote: "Subsequent lines are runner events: run_started, " + "task_started, line, task_completed, run_completed, notice. " + - "See prenup/docs/SCHEMA.md.", + "See docs/SCHEMA.md.", }) return s } diff --git a/internal/versioncheck/versioncheck.go b/internal/versioncheck/versioncheck.go index fe5e4f4..9ba299e 100644 --- a/internal/versioncheck/versioncheck.go +++ b/internal/versioncheck/versioncheck.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -14,11 +15,15 @@ import ( ) const ( - repoOwner = "c2fo" - repoName = "prenup" - tagPrefix = "v" - apiTimeout = 10 * time.Second - releasesURL = "https://api.github.com/repos/%s/%s/releases" + repoOwner = "c2fo" + repoName = "prenup" + tagPrefix = "v" + apiTimeout = 10 * time.Second + // per_page=100 is the GitHub API maximum. We do not paginate: the + // newest valid semver-tagged release is virtually always in the most + // recent 100 entries, and prenup would rather report "no release + // found" than fan out multiple HTTP calls on a pre-commit hook path. + releasesURL = "https://api.github.com/repos/%s/%s/releases?per_page=100" ) // Release represents a GitHub release. @@ -105,8 +110,8 @@ func fetchLatestPrenupRelease(ctx context.Context, githubToken, apiURL string) ( defer func() { _ = resp.Body.Close() }() if resp.StatusCode == http.StatusForbidden && resp.Header.Get("X-RateLimit-Remaining") == "0" { - return "", "", fmt.Errorf("GitHub API rate limit exceeded (resets at %s)", - resp.Header.Get("X-RateLimit-Reset")) + return "", "", fmt.Errorf("GitHub API rate limit exceeded (%s)", + formatRateLimitReset(resp.Header.Get("X-RateLimit-Reset"))) } if resp.StatusCode != http.StatusOK { return "", "", fmt.Errorf("GitHub API returned status %d", resp.StatusCode) @@ -141,6 +146,23 @@ func fetchLatestPrenupRelease(ctx context.Context, githubToken, apiURL string) ( return latestVer, latestURL, nil } +// formatRateLimitReset renders the GitHub X-RateLimit-Reset header (a Unix +// timestamp in seconds) as both a human-readable UTC time and a duration +// until reset. Falls back to the raw value on parse failure so a +// misformatted header never masks the underlying rate-limit condition. +func formatRateLimitReset(raw string) string { + secs, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 64) + if err != nil { + return "reset time unavailable" + } + reset := time.Unix(secs, 0).UTC() + wait := time.Until(reset).Round(time.Second) + if wait < 0 { + wait = 0 + } + return fmt.Sprintf("resets in %s at %s", wait, reset.Format(time.RFC3339)) +} + func normalizeVersion(v string) string { v = strings.TrimSpace(v) if !strings.HasPrefix(v, "v") { diff --git a/internal/versioncheck/versioncheck_test.go b/internal/versioncheck/versioncheck_test.go index 469dc4a..a795397 100644 --- a/internal/versioncheck/versioncheck_test.go +++ b/internal/versioncheck/versioncheck_test.go @@ -275,8 +275,34 @@ func TestCheck_RateLimited(t *testing.T) { result := checkAgainstReleasesAPI(context.Background(), "v1.0.0", "", server.URL) require.Error(t, result.Error) - assert.Contains(t, result.Error.Error(), "rate limit exceeded") - assert.Contains(t, result.Error.Error(), "1700000000") + msg := result.Error.Error() + assert.Contains(t, msg, "rate limit exceeded") + // The raw Unix timestamp 1700000000 is 2023-11-14T22:13:20Z; the + // formatter renders it as an RFC3339 UTC time so operators aren't left + // squinting at a Unix epoch. Assert against the human-readable form. + assert.Contains(t, msg, "2023-11-14T22:13:20Z") + assert.Contains(t, msg, "resets in") +} + +// TestFormatRateLimitReset covers the parse-fallback branch: a header the +// GitHub API doesn't set (or that we somehow received malformed) must not +// mask the underlying rate-limit condition with a strconv error. +func TestFormatRateLimitReset(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {name: "valid unix seconds", in: "1700000000", want: "2023-11-14T22:13:20Z"}, + {name: "empty header", in: "", want: "reset time unavailable"}, + {name: "non-numeric header", in: "later", want: "reset time unavailable"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := formatRateLimitReset(tc.in) + assert.Contains(t, got, tc.want) + }) + } } func TestCheck_InvalidCurrentVersion(t *testing.T) { From 13a2b6ea611bb97804afe34cfe6b0ac43a068b47 Mon Sep 17 00:00:00 2001 From: John Judd Date: Sat, 4 Jul 2026 17:55:09 -0500 Subject: [PATCH 6/7] update logo, remove superpowers plan --- .../2026-07-03-open-source-prenup-design.md | 204 ------------------ prenup.png | Bin 273730 -> 87801 bytes 2 files changed, 204 deletions(-) delete mode 100644 docs/superpowers/specs/2026-07-03-open-source-prenup-design.md diff --git a/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md b/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md deleted file mode 100644 index b4d3933..0000000 --- a/docs/superpowers/specs/2026-07-03-open-source-prenup-design.md +++ /dev/null @@ -1,204 +0,0 @@ -# Open-Sourcing `prenup` as `github.com/c2fo/prenup` - -**Status:** Approved -**Date:** 2026-07-03 -**Owner:** @jjudd - -## Goal - -Extract `prenup` from the internal `ep-tools` monorepo into a standalone open-source -repository at `github.com/c2fo/prenup`, using `github.com/c2fo/releasegen` as the -scaffolding template. Ship as `v0.1.0`. - -## Source, Target, Reference - -- **Source (internal):** `/Users/john.judd/gitprojects/ep-tools/prenup` - (module `github.com/c2fo/ep-tools/prenup/v2`) -- **Target (fresh repo, remote pre-configured):** `/Users/john.judd/gitprojects/prenup` - (module will be `github.com/c2fo/prenup`) -- **Reference (scaffolding template):** `/Users/john.judd/gitprojects/releasegen` - -## Design - -### 1. Repo bootstrap — fresh history - -Copy the working tree from `ep-tools/prenup` into `gitprojects/prenup`. Do not -carry any internal git history; the first commit on `main` is the open-source -baseline. Overwrite the stub `README.md`. Exclude `.git`, IDE files, and any -build artifacts. - -### 2. Module + import-path rename - -Rename `github.com/c2fo/ep-tools/prenup/v2` → `github.com/c2fo/prenup` (drop the -`/v2` major-version suffix since we're resetting to `v0.1.0`). Rewrite every -import in `.go` sources, update `go.mod`, update `.mockery.yaml`, regenerate -mocks, run `go mod tidy`, and confirm `go build ./...` and `go test ./...` still -pass. - -### 3. Scrub internal references - -Based on the audit report, apply these changes: - -**Code:** -- `internal/versioncheck/versioncheck.go`: `repoOwner="c2fo"`, `repoName="prenup"`, - `tagPrefix="v"` (was `C2FO`/`ep-tools`/`prenup/`). -- `internal/cli/version.go`: remove `strings.TrimPrefix(..., "prenup/")` calls - (module version strings will now be plain `vX.Y.Z`). -- `internal/ui/agent.go`: `HomepageURL = "https://github.com/c2fo/prenup"`. -- `internal/config/migrate.go`: update the docs-URL header comment. -- `internal/cli/run.go`: update the install-command hint string - (`go install github.com/c2fo/prenup/cmd/prenup@latest`). -- `internal/versioncheck/versioncheck_test.go`: update fixture URLs to - `c2fo/prenup`; remove or repoint the `rowtater` / `releasegen` sibling-tag - fixtures. - -**Config/schemas/assets:** -- `assets/prenup.schema.json` + `internal/config/schema.json`: update `$id` to - `https://raw.githubusercontent.com/c2fo/prenup/main/assets/prenup.schema.json`. -- `.mockery.yaml`: update package path. -- `prenup.example.yaml`: replace the `.github/workflows/scripts/changelog.sh` and - `make docs` tasks with generic standalone examples (e.g. `go test`, `go vet`, - a `releasegen validate` hook — same pattern as sibling projects). - -**Docs:** -- `README.md`: rewrite for a public audience — what prenup is (git-hook manager), - install via `go install`, quickstart, config reference. Fix the - `../LICENSE` reference to `LICENSE.md` in-repo. -- `docs/PRD.md`, `docs/V2_PLAN.md`, `docs/SCHEMA.md`, `docs/FUTURE.md`: scrub - monorepo-relative paths (`prenup/README.md` → `README.md`), update module - paths and URLs. Consider whether `V2_PLAN.md` is worth keeping in the public - repo (historical rewrite plan); if not, drop it. -- `cmd/prenup/main.go` comment: fix monorepo-relative doc paths. -- `internal/config/schema_test.go`: fix the byte-identity error message paths. - -### 4. Open-source scaffolding (from releasegen) - -Add these files, adapted for prenup: - -| File | Adaptation | -| --- | --- | -| `LICENSE.md` | Copy verbatim (MIT, C2FO Inc.) | -| `CODE_OF_CONDUCT.md` | Copy verbatim | -| `.golangci.yml` | Copy; change `prefix` / `local-prefixes` to `github.com/c2fo/prenup` | -| `.testcoverage.yml` | Copy; leave thresholds — tune after first CI run | -| `.github/ISSUE_TEMPLATE/{bug_report,feature_request,custom}.md` | Copy; retarget prenup | -| `.github/workflows/ci.yml` | Copy verbatim | -| `.github/workflows/golangci-lint.yml` | Copy verbatim | -| `.github/workflows/codeql.yml` | Copy verbatim | -| `.github/workflows/go-test-coverage.yml` | Copy verbatim | -| `.github/workflows/validate-changelog.yml` | Copy verbatim (already uses `releasegen validate`) | - -Note: **not** copying releasegen's `deployments/Dockerfile` — prenup does not -ship a container. - -### 5. Release workflow — vfs-style (tag-only) - -Copy `.github/workflows/releasegen.yml` from **vfs**, not releasegen. Rationale: -releasegen's own workflow builds itself and pushes a Docker image (self-release -special case); vfs's workflow just runs the pre-built -`ghcr.io/c2fo/releasegen:v1` image to cut a tag and GitHub release from the -changelog — which is exactly what prenup needs. - -Also add a `.releasegen.yaml` at the repo root (adapted from releasegen's), so -the release and validate workflows share one source of truth for change-type -config. - -**Distribution:** users install via `go install github.com/c2fo/prenup/cmd/prenup@latest`. -No Docker image, no GoReleaser binaries. - -### 6. CHANGELOG reset - -Replace the entire `CHANGELOG.md` with: - -```markdown -# Changelog - -All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.0] - 2026-07-04 - -### Added -- Initial open-source release of `prenup`, a git pre-commit hook manager. -- `run`, `plan`, `install`/`uninstall`, `init`, `migrate`, `config`, `version` - commands. -- v2 YAML configuration schema with published JSON Schema for editor support. -- Change detection with module-marker discovery for monorepo-friendly parallel - execution. -- Human (Bubble Tea TUI), NDJSON, and markdown output modes. -- Update check against GitHub Releases. -``` - -The internal `v2.x` history is dropped entirely. - -### 7. Dogfooding config - -Copy the releasegen `.prenup.yaml` template, adapted for a Go project of -prenup's shape (lint, test, changelog-validate). This makes prenup use itself. - -## Validation - -Before pushing to `main`: - -1. `go build ./...` and `go test ./...` pass in the new repo. -2. `golangci-lint run` passes. -3. `grep -r "ep-tools\|C2FO/ep-tools" .` returns nothing (outside `docs/` - historical mentions that we consciously keep, if any). -4. The scaffolding workflows are syntactically valid (YAML parse). -5. A manual local `prenup run` on the new repo succeeds (dogfood). - -## Out of scope - -- Docker image publishing. -- Cross-platform binary release (GoReleaser). -- CONTRIBUTING.md, SECURITY.md, Makefile (neither reference project has them). -- Rewriting the internal `docs/V2_PLAN.md` — will drop it unless there's a - reason to keep it public. -- Any code refactoring beyond what's needed to remove monorepo assumptions. - -## Addendum (2026-07-04): config versioning redesign - -Decisions made after the initial extraction, before the first public commit: - -1. **Drop the `migrate` command.** The v1→v2 config migration only ever - applied to the internal ep-tools format; no public v1 config exists. Remove - `internal/cli/migrate.go`, `internal/config/migrate.go` (+ tests), the CLI - registration, and all migration docs. Relocate the still-needed `Marshal` - helper from `migrate.go` into `config.go`. - -2. **Reset the public config baseline to `version: 1`.** The first public - config format is version 1 (was `version: 2` from the internal history). - Update the schema (`const: 1`), validator, defaults, example config, and - docs. - -3. **Config schema version is a plain integer, decoupled from the prenup - release version.** It increments *only* on a breaking config-format change. - Additive, non-breaking changes require neither a version bump nor a - migration — new optional fields default sensibly and old configs keep - working. - -4. **Version handling now (minimal):** - - Accept `version: 1`. - - Reject a *newer* version (e.g. `version: 2` seen by an older binary) with - a clear "this config requires a newer version of prenup" error. - - Reject unknown/missing/older-than-1 with the existing "unsupported - version" style error. - - Defer any in-memory adaptation layer and a `config upgrade` command until - `version: 2` actually exists (nothing to convert from yet). The policy is - documented in DESIGN.md so the contract is set. - -5. **Discoverability header.** Scaffolded configs (`prenup init`) lead with a - comment block identifying the tool and linking to the project, so anyone - who encounters a `.prenup.yaml` can find out what it is. If prenup ever - rewrites a config, it must re-emit this header. - -## Non-negotiables recap - -- Fresh git history. -- Module path: `github.com/c2fo/prenup` (no `/v2`). -- Changelog resets to `v0.1.0`. -- Scaffolding mirrors releasegen; release workflow mirrors vfs. -- Distribution is `go install`-only. diff --git a/prenup.png b/prenup.png index 901049fca95de8e9fec2d8372372b8a547787414..77a62782725562e97b6f7e728c9c4edc6c9b285b 100644 GIT binary patch literal 87801 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 z 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
  • g4IeQwl&~BAik>6Gv}O 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 dK^D<8bMAY1TL@ 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 Vi_Kio<2e8T002ovPDHLkV1gmBJWBuo From d5fe280c6bb0648570f084dcf215ce32d061c065 Mon Sep 17 00:00:00 2001 From: John Judd Date: Sat, 4 Jul 2026 18:18:38 -0500 Subject: [PATCH 7/7] fix: address second round of PR #1 review comments Resolves the follow-up Copilot review comments on PR #1: - internal/lock/lock.go: correct the Acquire godoc, which incorrectly said Close removes the lock file (it intentionally does not, to avoid an unlink/unlock TOCTOU race). - internal/lock/lock.go: resolve a relative `gitdir:` target in a `.git` file (worktrees/submodules) against repoRoot instead of the current working directory, so the lock lands in the real git metadata dir. - internal/lock/flock_other.go: add a `!unix` build-tagged no-op flock stub so the package (and module) compiles on non-unix platforms. Advisory locking stays unix-only by design, as the package doc states. - internal/ui/human/summary.go: bound the retained output buffer to the most recent maxSummaryLogLines lines (keeping a tail and reporting how many were dropped) so a noisy task can't grow memory without limit. - internal/versioncheck/versioncheck.go: send a User-Agent header on the Releases API request; GitHub expects one and can 403 without it. - internal/cli/run_helpers_test.go: drop a no-op strings.TrimSuffix(.,"") and return the buffer directly. Adds tests for the relative-gitdir resolution and the summary log bound. --- CHANGELOG.md | 14 +++++++ internal/cli/run_helpers_test.go | 3 +- internal/lock/flock_other.go | 19 +++++++++ internal/lock/lock.go | 10 ++++- internal/lock/lock_test.go | 24 +++++++++++ internal/ui/human/summary.go | 45 +++++++++++++++------ internal/ui/human/summary_test.go | 58 +++++++++++++++++++++++++++ internal/versioncheck/versioncheck.go | 3 ++ 8 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 internal/lock/flock_other.go create mode 100644 internal/ui/human/summary_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3012b04..e2b0ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `internal/ui/jsonout/jsonout.go` (including the agent-facing `event_types_note` hint) now point at `docs/SCHEMA.md`, matching the actual in-repo path. +- Repo lock now resolves a *relative* `gitdir:` target in a `.git` file + (git worktrees/submodules) against the repository root instead of the + current working directory, so the lock lands in the real git metadata + directory. +- Human-mode post-run summary bounds its retained output to the most + recent lines (keeping a tail and noting how many were dropped) so a + noisy task can't grow memory without limit during a hook run. +- Version-check requests now send a `User-Agent` header, which the GitHub + REST API expects (some environments 403 without it). +- `lock` package now compiles on non-unix platforms via a build-tagged + no-op `flock` stub; advisory locking remains unix-only by design, as + documented. +- Corrected the `lock.Acquire` godoc, which incorrectly stated that + `Close` removes the lock file (it intentionally does not). ### Added - Initial open-source release. diff --git a/internal/cli/run_helpers_test.go b/internal/cli/run_helpers_test.go index 65ef445..498d9e5 100644 --- a/internal/cli/run_helpers_test.go +++ b/internal/cli/run_helpers_test.go @@ -5,7 +5,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -203,5 +202,5 @@ func captureStdout(t *testing.T, fn func() error) string { buf, readErr := io.ReadAll(r) require.NoError(t, readErr) require.NoError(t, fnErr) - return strings.TrimSuffix(string(buf), "") + return string(buf) } diff --git a/internal/lock/flock_other.go b/internal/lock/flock_other.go new file mode 100644 index 0000000..c828223 --- /dev/null +++ b/internal/lock/flock_other.go @@ -0,0 +1,19 @@ +//go:build !unix + +package lock + +import "os" + +// This stub keeps the lock package (and therefore the whole module) +// compiling on non-unix platforms, where flock(2) is unavailable. Prenup's +// task execution already assumes a unix-like environment (tasks run via +// `bash -c`), so the cross-repo serialization guarantee is unix-only by +// design; here Acquire degrades to a no-op lock rather than failing to +// build. The package doc calls out that real advisory locking is unix-only. + +// flockNB is a no-op on non-unix platforms: it always "succeeds" so a +// single prenup run proceeds, but it provides no cross-process exclusion. +func flockNB(*os.File) error { return nil } + +// funlock is a no-op on non-unix platforms. +func funlock(*os.File) error { return nil } diff --git a/internal/lock/lock.go b/internal/lock/lock.go index 6210e51..fdfee6d 100644 --- a/internal/lock/lock.go +++ b/internal/lock/lock.go @@ -41,7 +41,8 @@ type Lock struct { // the problem. // // The returned Lock owns the file descriptor; the caller must call Close -// (typically via defer) to release the lock and remove the file. +// (typically via defer) to release the lock. Close does not delete the +// lock file (see Close for the rationale). func Acquire(repoRoot string) (*Lock, error) { gitDir := filepath.Join(repoRoot, ".git") // Repos with separate gitdirs (worktrees, submodules) write a `.git` @@ -51,6 +52,13 @@ func Acquire(repoRoot string) (*Lock, error) { // having pointed us at a real repo root. if info, err := os.Stat(gitDir); err == nil && !info.IsDir() { if resolved, ok := resolveGitFile(gitDir); ok { + // A `gitdir:` target may be relative to the directory that + // contains the `.git` file (repoRoot). Anchor it there so + // MkdirAll/OpenFile don't operate relative to the current + // working directory. + if !filepath.IsAbs(resolved) { + resolved = filepath.Join(repoRoot, resolved) + } gitDir = resolved } } diff --git a/internal/lock/lock_test.go b/internal/lock/lock_test.go index 851482d..be46b41 100644 --- a/internal/lock/lock_test.go +++ b/internal/lock/lock_test.go @@ -74,6 +74,30 @@ func TestAcquireCreatesGitDirIfMissing(t *testing.T) { require.True(t, info.IsDir()) } +// TestAcquireResolvesRelativeGitdir covers the worktree/submodule layout +// where `.git` is a regular file whose `gitdir:` target is *relative* to +// the directory containing that file. Acquire must anchor the resolved path +// at repoRoot so the lock lands in the real git metadata dir rather than +// relative to the current working directory. +func TestAcquireResolvesRelativeGitdir(t *testing.T) { + t.Parallel() + repo := t.TempDir() + + // Real gitdir lives at /.real-git; the `.git` file points at it + // with a relative path, exactly as `git worktree add` writes it. + realGitDir := filepath.Join(repo, ".real-git") + require.NoError(t, os.MkdirAll(realGitDir, 0o750)) + require.NoError(t, os.WriteFile( + filepath.Join(repo, ".git"), []byte("gitdir: .real-git\n"), 0o600)) + + l, err := Acquire(repo) + require.NoError(t, err) + defer func() { _ = l.Close() }() + + require.Equal(t, filepath.Join(realGitDir, LockFileName), l.Path()) + require.FileExists(t, l.Path()) +} + func TestAcquireConcurrent(t *testing.T) { t.Parallel() repo := fakeRepo(t) diff --git a/internal/ui/human/summary.go b/internal/ui/human/summary.go index 470f3c0..29ffd12 100644 --- a/internal/ui/human/summary.go +++ b/internal/ui/human/summary.go @@ -10,21 +10,29 @@ import ( "github.com/c2fo/prenup/internal/runner" ) +// maxSummaryLogLines caps how many output lines SummarySink retains for the +// post-run "Prenup output" block. A noisy task (verbose test suite, chatty +// linter) could otherwise grow s.logs without bound during a single hook +// run. When the cap is exceeded we keep the most recent lines -- the tail is +// where failures and stack traces surface -- and note how many were dropped. +const maxSummaryLogLines = 5000 + // SummarySink collects events and prints a human-readable post-run summary to // stdout after the alt-screen UI exits. Used in combination with the // RunnerSink TUI to preserve visibility in clients that hide the alt-screen. type SummarySink struct { - mu sync.Mutex - w io.Writer - version string - notice string - logs []string - taskOrder []string - seenTask map[string]bool - statuses map[string]runner.TaskStatus - durations map[string]int64 - notes map[string]string - summary runner.Event + mu sync.Mutex + w io.Writer + version string + notice string + logs []string + droppedLines int + taskOrder []string + seenTask map[string]bool + statuses map[string]runner.TaskStatus + durations map[string]int64 + notes map[string]string + summary runner.Event } // NewSummarySink returns a SummarySink writing to w. @@ -47,7 +55,7 @@ func (s *SummarySink) Emit(ev runner.Event) { s.version = ev.Version s.notice = ev.Message case runner.EventLine: - s.logs = append(s.logs, ev.Text) + s.appendLog(ev.Text) case runner.EventTaskStarted: if !s.seenTask[ev.Task] { s.seenTask[ev.Task] = true @@ -68,6 +76,16 @@ func (s *SummarySink) Emit(ev runner.Event) { } } +// appendLog records an output line, enforcing maxSummaryLogLines by +// dropping the oldest line once the cap is reached. Caller holds s.mu. +func (s *SummarySink) appendLog(line string) { + if len(s.logs) >= maxSummaryLogLines { + s.logs = s.logs[1:] + s.droppedLines++ + } + s.logs = append(s.logs, line) +} + // Close flushes the summary to the writer. func (s *SummarySink) Close() error { s.mu.Lock() @@ -92,6 +110,9 @@ func (s *SummarySink) render() { s.writef("%s\n", s.notice) } s.writef("\nPrenup output:\n----------------\n") + if s.droppedLines > 0 { + s.writef("... (%d earlier line(s) omitted to bound memory) ...\n", s.droppedLines) + } for _, line := range s.logs { s.writeln(line) } diff --git a/internal/ui/human/summary_test.go b/internal/ui/human/summary_test.go new file mode 100644 index 0000000..46ac303 --- /dev/null +++ b/internal/ui/human/summary_test.go @@ -0,0 +1,58 @@ +package human + +import ( + "bytes" + "fmt" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/c2fo/prenup/internal/runner" +) + +// TestSummarySinkBoundsLogs verifies the memory guard on the output buffer: +// once more than maxSummaryLogLines EventLine events arrive, only the most +// recent lines are retained and the rendered summary notes how many were +// dropped. +func TestSummarySinkBoundsLogs(t *testing.T) { + t.Parallel() + + var buf bytes.Buffer + s := NewSummarySink(&buf) + + const extra = 25 + total := maxSummaryLogLines + extra + for i := range total { + s.Emit(runner.Event{Kind: runner.EventLine, Text: fmt.Sprintf("line-%d", i)}) + } + + assert.Len(t, s.logs, maxSummaryLogLines, "retained lines must be capped") + assert.Equal(t, extra, s.droppedLines, "dropped count must track the overflow") + // Oldest lines are evicted; the tail is preserved. + assert.Equal(t, "line-"+strconv.Itoa(total-1), s.logs[len(s.logs)-1]) + assert.Equal(t, "line-"+strconv.Itoa(extra), s.logs[0]) + + require.NoError(t, s.Close()) + out := buf.String() + assert.Contains(t, out, "earlier line(s) omitted") + assert.Contains(t, out, "line-"+strconv.Itoa(total-1), "most recent line should render") + assert.NotContains(t, out, "line-0\n", "evicted oldest line should not render") +} + +// TestSummarySinkNoTruncationNotice confirms the truncation notice is absent +// when output stays under the cap. +func TestSummarySinkNoTruncationNotice(t *testing.T) { + t.Parallel() + + var buf bytes.Buffer + s := NewSummarySink(&buf) + s.Emit(runner.Event{Kind: runner.EventLine, Text: "only line"}) + require.NoError(t, s.Close()) + + out := buf.String() + assert.Equal(t, 0, s.droppedLines) + assert.NotContains(t, out, "omitted") + assert.Contains(t, out, "only line") +} diff --git a/internal/versioncheck/versioncheck.go b/internal/versioncheck/versioncheck.go index 9ba299e..3eec48d 100644 --- a/internal/versioncheck/versioncheck.go +++ b/internal/versioncheck/versioncheck.go @@ -99,6 +99,9 @@ func fetchLatestPrenupRelease(ctx context.Context, githubToken, apiURL string) ( return "", "", err } req.Header.Set("Accept", "application/vnd.github.v3+json") + // GitHub's REST API requires a User-Agent; requests without one can be + // rejected with 403 and are harder to trace in server logs. + req.Header.Set("User-Agent", repoName) if githubToken != "" { req.Header.Set("Authorization", "Bearer "+githubToken) }