diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..db1caa2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,96 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{md,markdown}] +trim_trailing_whitespace = false + +[*.{yml,yaml,json}] +indent_size = 2 + +[*.{csproj,props,targets,slnx,xml}] +indent_size = 2 + +[*.cs] +csharp_indent_case_contents = true +csharp_indent_labels = flush_left +csharp_indent_switch_labels = true +csharp_preserve_single_line_blocks = true +csharp_new_line_before_open_brace = all +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_name_and_opening_parenthesis = true +csharp_space_between_method_declaration_name_and_open_parenthesis = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = false:suggestion +csharp_style_var_elsewhere = false:suggestion +csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_constructors = false:suggestion +csharp_style_expression_bodied_operators = false:suggestion +csharp_style_expression_bodied_local_functions = false:suggestion +csharp_style_expression_bodied_properties = when_on_single_line:suggestion +csharp_style_expression_bodied_indexers = when_on_single_line:suggestion +csharp_style_expression_bodied_accessors = when_on_single_line:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_prefer_simple_using_statement = true:suggestion +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_style_prefer_auto_properties = false:none +csharp_prefer_braces = true:suggestion +csharp_prefer_static_local_function = true:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +resharper_space_before_new_parentheses = true +resharper_space_before_method_call_parentheses = true +resharper_space_before_method_parentheses = true +resharper_space_before_empty_method_call_parentheses = true +resharper_space_before_empty_method_parentheses = true +resharper_space_before_typeof_parentheses = true +resharper_space_before_default_parentheses = true +resharper_space_before_checked_parentheses = true +resharper_space_before_sizeof_parentheses = true +resharper_space_before_nameof_parentheses = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cd0aac0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: ['**'] + pull_request: + branches: [main, develop] + +permissions: + contents: read + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + dotnet-quality: 'preview' + + - name: Restore + run: dotnet restore Terminal.Gui.Cli.slnx + + - name: Build + run: dotnet build Terminal.Gui.Cli.slnx --no-restore -c Debug + + - name: Verify code style + if: matrix.os == 'ubuntu-latest' + run: dotnet format Terminal.Gui.Cli.slnx --no-restore --verify-no-changes + + - name: Terminal.Gui.Cli.Tests + run: dotnet run --project tests/Terminal.Gui.Cli.Tests --no-build -c Debug + + - name: Terminal.Gui.Cli.IntegrationTests + run: dotnet run --project tests/Terminal.Gui.Cli.IntegrationTests --no-build -c Debug + + - name: Terminal.Gui.Cli.SmokeTests + run: dotnet run --project tests/Terminal.Gui.Cli.SmokeTests --no-build -c Debug diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml new file mode 100644 index 0000000..0f49ca9 --- /dev/null +++ b/.github/workflows/finalize-release.yml @@ -0,0 +1,104 @@ +name: Finalize Release + +on: + pull_request: + types: [closed] + branches: [main] + +jobs: + finalize-release: + name: Tag, Release, and Back-merge + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout release merge commit + uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + fetch-depth: 0 + token: ${{ secrets.RELEASE_PAT }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Extract release info from branch name + id: release + env: + RELEASE_BRANCH: ${{ github.event.pull_request.head.ref }} + shell: bash + run: | + BRANCH="$RELEASE_BRANCH" + TAG="${BRANCH#release/}" + SEMVER="${TAG#v}" + + if echo "$SEMVER" | grep -q '-'; then + PRERELEASE="true" + else + PRERELEASE="false" + fi + + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "semver=${SEMVER}" >> "$GITHUB_OUTPUT" + echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT" + + - name: Check if tag already exists + shell: bash + run: | + if git rev-parse "${{ steps.release.outputs.tag }}" >/dev/null 2>&1; then + echo "::error::Tag ${{ steps.release.outputs.tag }} already exists." + exit 1 + fi + + - name: Create annotated tag + shell: bash + run: | + TAG="${{ steps.release.outputs.tag }}" + git tag -a "$TAG" -m "Release $TAG" + git push origin "$TAG" + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + TAG: ${{ steps.release.outputs.tag }} + PRERELEASE: ${{ steps.release.outputs.prerelease }} + shell: bash + run: | + PRERELEASE_FLAG="" + if [ "$PRERELEASE" = "true" ]; then + PRERELEASE_FLAG="--prerelease" + fi + + gh release create "$TAG" \ + --title "$TAG" \ + --generate-notes \ + $PRERELEASE_FLAG + + - name: Delete release branch + env: + RELEASE_BRANCH: ${{ github.event.pull_request.head.ref }} + shell: bash + run: | + BRANCH="$RELEASE_BRANCH" + git push origin --delete "$BRANCH" || true + + - name: Create back-merge PR + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + TAG: ${{ steps.release.outputs.tag }} + shell: bash + run: | + BACKMERGE_BRANCH="backmerge/${TAG}" + + git checkout -b "$BACKMERGE_BRANCH" "${{ github.event.pull_request.merge_commit_sha }}" + git push origin "$BACKMERGE_BRANCH" + + gh pr create \ + --base develop \ + --head "$BACKMERGE_BRANCH" \ + --title "Back-merge ${TAG} from main into develop" \ + --body "Automatic back-merge after release ${TAG}. Merge this to keep \`develop\` in sync with \`main\`." diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..4f3a5bc --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,167 @@ +name: Prepare Release + +on: + workflow_dispatch: + inputs: + release_type: + description: 'Release type' + required: true + type: choice + options: + - beta + - rc + - stable + default: stable + version_override: + description: 'Version override (optional, e.g., 0.1.0). Defaults to Directory.Build.props without -develop.' + required: false + type: string + +concurrency: + group: prepare-release + cancel-in-progress: false + +jobs: + prepare-release: + name: Create Release PR + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout develop + uses: actions/checkout@v5 + with: + ref: develop + fetch-depth: 0 + token: ${{ secrets.RELEASE_PAT }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Compute release version + id: version + shell: bash + run: | + if [ -n "${{ github.event.inputs.version_override }}" ]; then + VERSION="${{ github.event.inputs.version_override }}" + else + VERSION=$(sed -n 's|.*\(.*\).*|\1|p' Directory.Build.props | head -1) + VERSION="${VERSION%%-*}" + fi + + RELEASE_TYPE="${{ github.event.inputs.release_type }}" + + if [ -z "$VERSION" ]; then + echo "::error::Could not resolve release version." + exit 1 + fi + + if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Release version must be a stable SemVer core like 0.1.0; got '$VERSION'." + exit 1 + fi + + if [ "$RELEASE_TYPE" = "stable" ]; then + SEMVER="$VERSION" + TAG="v${VERSION}" + else + LATEST_TAG=$(git tag -l "v${VERSION}-${RELEASE_TYPE}.*" --sort=-v:refname | head -1) + if [ -z "$LATEST_TAG" ]; then + NEXT_NUM=1 + else + CURRENT_NUM="${LATEST_TAG##*.}" + NEXT_NUM=$((CURRENT_NUM + 1)) + fi + + SEMVER="${VERSION}-${RELEASE_TYPE}.${NEXT_NUM}" + TAG="v${SEMVER}" + fi + + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "semver=${SEMVER}" >> "$GITHUB_OUTPUT" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "release_type=${RELEASE_TYPE}" >> "$GITHUB_OUTPUT" + + - name: Check for conflicts + shell: bash + run: | + TAG="${{ steps.version.outputs.tag }}" + BRANCH="release/${TAG}" + + if git rev-parse "${TAG}" >/dev/null 2>&1; then + echo "::error::Tag ${TAG} already exists." + exit 1 + fi + + if git ls-remote --heads origin "${BRANCH}" | grep -q .; then + echo "::error::Branch ${BRANCH} already exists on the remote." + exit 1 + fi + + if curl -fsS https://api.nuget.org/v3-flatcontainer/terminal.gui.cli/index.json \ + | jq -r '.versions[]' | grep -Fxq "${{ steps.version.outputs.semver }}"; then + echo "::error::Terminal.Gui.Cli ${{ steps.version.outputs.semver }} already exists on NuGet.org." + echo "::error::Choose a new version_override." + exit 1 + fi + + if gh pr list --state open --base main --head "${BRANCH}" --json number --jq 'length' | grep -vq '^0$'; then + echo "::error::An open release PR already exists for ${BRANCH}." + exit 1 + fi + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + + - name: Create release branch + shell: bash + run: | + BRANCH="release/${{ steps.version.outputs.tag }}" + git checkout -b "$BRANCH" + git push origin "$BRANCH" + + - name: Create Pull Request + env: + GH_TOKEN: ${{ secrets.RELEASE_PAT }} + SEMVER: ${{ steps.version.outputs.semver }} + TAG: ${{ steps.version.outputs.tag }} + RELEASE_TYPE: ${{ steps.version.outputs.release_type }} + shell: bash + run: | + BRANCH="release/${TAG}" + + if [ "$RELEASE_TYPE" = "stable" ]; then + PRERELEASE_NOTE="" + else + PRERELEASE_NOTE="This is a **${RELEASE_TYPE}** pre-release." + fi + + cat > /tmp/pr_body.md << EOF2 + ## Release ${TAG} + + ${PRERELEASE_NOTE} + + **Version:** \`${SEMVER}\` + **NuGet Package:** \`Terminal.Gui.Cli ${SEMVER}\` + + ### What happens when this PR is merged + + 1. The **Finalize Release** workflow creates tag \`${TAG}\` + 2. The **Release** workflow builds and pushes \`Terminal.Gui.Cli ${SEMVER}\` to NuGet.org + 3. A **GitHub Release** is created with auto-generated notes + 4. A **back-merge PR** from \`main\` -> \`develop\` is opened + + ### Checklist + + - [ ] CI passes on this PR + - [ ] Version looks correct: \`${SEMVER}\` + - [ ] Release notes reviewed + EOF2 + + gh pr create \ + --base main \ + --head "$BRANCH" \ + --title "Release ${TAG}" \ + --body-file /tmp/pr_body.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4b78ecc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,110 @@ +name: Release + +on: + push: + tags: ['v*'] + branches: [develop] + +permissions: + contents: write + +concurrency: + group: release + cancel-in-progress: false + +jobs: + resolve-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.v.outputs.version }} + steps: + - uses: actions/checkout@v5 + + - name: Compute version + id: v + shell: bash + run: | + if [ "${{ github.ref_type }}" = "tag" ]; then + VERSION="${GITHUB_REF_NAME#v}" + elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then + BASE=$(sed -n 's|.*\(.*\).*|\1|p' Directory.Build.props | head -1) + if [ -z "$BASE" ]; then + echo "::error::Could not read from Directory.Build.props." + exit 1 + fi + VERSION="${BASE}.${GITHUB_RUN_NUMBER}" + else + echo "::error::Unsupported trigger: event=${{ github.event_name }} ref=${{ github.ref }}" + exit 1 + fi + + if [ -z "$VERSION" ]; then + echo "::error::Could not resolve version." + exit 1 + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + build-and-test: + needs: resolve-version + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + env: + VERSION: ${{ needs.resolve-version.outputs.version }} + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + dotnet-quality: 'preview' + + - name: Restore + run: dotnet restore Terminal.Gui.Cli.slnx + + - name: Build + run: dotnet build Terminal.Gui.Cli.slnx --no-restore -c Release -p:Version=${{ env.VERSION }} + + - name: Terminal.Gui.Cli.Tests + run: dotnet run --project tests/Terminal.Gui.Cli.Tests --no-build -c Release + + - name: Terminal.Gui.Cli.IntegrationTests + run: dotnet run --project tests/Terminal.Gui.Cli.IntegrationTests --no-build -c Release + + - name: Terminal.Gui.Cli.SmokeTests + run: dotnet run --project tests/Terminal.Gui.Cli.SmokeTests --no-build -c Release + + pack-and-publish: + needs: [resolve-version, build-and-test] + runs-on: ubuntu-latest + env: + VERSION: ${{ needs.resolve-version.outputs.version }} + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + dotnet-quality: 'preview' + + - name: Pack Terminal.Gui.Cli + run: dotnet pack src/Terminal.Gui.Cli -c Release -p:Version=${{ env.VERSION }} -o packages/ + + - name: Upload packages + uses: actions/upload-artifact@v4 + with: + name: nuget-packages + path: packages/*.nupkg + retention-days: 30 + + - name: Push to NuGet + run: > + dotnet nuget push packages/*.nupkg + --api-key ${{ secrets.NUGET_API_KEY }} + --source https://api.nuget.org/v3/index.json + --skip-duplicate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be5256d --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +**/bin/ +**/obj/ +.vs/ +.idea/ +*.user +*.suo +*.DotSettings.user +TestResults/ +packages/ +publish/ +*.nupkg +launchSettings.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ade21b0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,3 @@ +# AGENTS.md + +All AI coding agents (GitHub Copilot, Codex, etc.) must read and follow the rules in [CLAUDE.md](CLAUDE.md). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5126311 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,42 @@ +# CLAUDE.md + +This file provides guidance to AI coding agents working in this repository. + +## Project status + +This repository currently contains scaffolding only for `Terminal.Gui.Cli`. +No library implementation is present yet. + +## Source of truth + +- Engineering authority: `specs/constitution.md` +- Build/style defaults: `.editorconfig`, `Directory.Build.props` + +If guidance conflicts, follow `specs/constitution.md`. + +## Project structure + +- `src/Terminal.Gui.Cli` — class library package (`Terminal.Gui.Cli`) +- `tests/Terminal.Gui.Cli.Tests` — unit tests +- `tests/Terminal.Gui.Cli.IntegrationTests` — integration tests +- `tests/Terminal.Gui.Cli.SmokeTests` — smoke tests +- `examples/Terminal.Gui.Cli.ExampleApp` — sample console app + +## Build and test + +- `dotnet restore Terminal.Gui.Cli.slnx` +- `dotnet build Terminal.Gui.Cli.slnx --no-restore -c Debug` +- `dotnet format Terminal.Gui.Cli.slnx --no-restore --verify-no-changes` +- `dotnet run --project tests/Terminal.Gui.Cli.Tests --no-build -c Debug` +- `dotnet run --project tests/Terminal.Gui.Cli.IntegrationTests --no-build -c Debug` +- `dotnet run --project tests/Terminal.Gui.Cli.SmokeTests --no-build -c Debug` + +## Coding standards + +- Target framework is `net10.0`. +- Keep warnings at zero (`TreatWarningsAsErrors=true`). +- Use file-scoped namespaces for new C# files. +- Use Allman braces and always include braces for conditionals/loops. +- Prefer guard clauses / early returns to deep nesting. +- One type per file for non-trivial public/internal types. +- Do not add unrelated implementation while scaffolding. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..f0681d4 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,29 @@ + + + + net10.0 + latest + enable + enable + true + true + + gui-cs + gui-cs + Copyright (c) gui-cs and contributors + + 0.1.0-develop + https://github.com/gui-cs/cli + https://github.com/gui-cs/cli + git + LICENSE + + 2.4.1-develop.11 + + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..8c119d5 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,2 @@ + + diff --git a/Terminal.Gui.Cli.slnx b/Terminal.Gui.Cli.slnx new file mode 100644 index 0000000..2a828b3 --- /dev/null +++ b/Terminal.Gui.Cli.slnx @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Terminal.Gui.Cli.ExampleApp/Program.cs b/examples/Terminal.Gui.Cli.ExampleApp/Program.cs new file mode 100644 index 0000000..1c99635 --- /dev/null +++ b/examples/Terminal.Gui.Cli.ExampleApp/Program.cs @@ -0,0 +1 @@ +Console.WriteLine ("Terminal.Gui.Cli scaffold example app."); diff --git a/examples/Terminal.Gui.Cli.ExampleApp/Terminal.Gui.Cli.ExampleApp.csproj b/examples/Terminal.Gui.Cli.ExampleApp/Terminal.Gui.Cli.ExampleApp.csproj new file mode 100644 index 0000000..ba00716 --- /dev/null +++ b/examples/Terminal.Gui.Cli.ExampleApp/Terminal.Gui.Cli.ExampleApp.csproj @@ -0,0 +1,14 @@ + + + + Exe + Terminal.Gui.Cli.ExampleApp + Terminal.Gui.Cli.ExampleApp + false + + + + + + + diff --git a/specs/constitution.md b/specs/constitution.md new file mode 100644 index 0000000..0dc4dd2 --- /dev/null +++ b/specs/constitution.md @@ -0,0 +1,63 @@ +# gui-cs/cli Constitution + +**Version**: 1.1 | **Ratified**: 2026-05-23 | **Last Amended**: 2026-05-23 + +This constitution governs all contributions to `gui-cs/cli`. It is the highest-authority engineering document in this repository. + +## I. Purpose & Scope + +`Terminal.Gui.Cli` is a `.NET` class library for exposing `Terminal.Gui` capabilities through scriptable CLI surfaces. + +- Package ID: `Terminal.Gui.Cli` +- Namespace: `Terminal.Gui.Cli` +- TFM: `net10.0` + +## II. Non-Goals + +Until implementation begins, this repository remains scaffold-first and should not accrue speculative production features. + +## III. Architectural and Engineering Rules (C1-C8) + +Every PR must comply with all rules below. + +### C1 — Only CliHost calls Terminal.Gui lifecycle APIs + +Initialization and shutdown of Terminal.Gui runtime lifecycle APIs must be centralized in `CliHost`. No command, helper, or utility type may call lifecycle entrypoints directly. + +### C2 — Public API changes require spec updates + +Any change to public API surface must include corresponding updates in `specs/` in the same PR. + +### C3 — No reflection-based command discovery + +Command discovery must be explicit and deterministic. Reflection scanning for command registration or dispatch is prohibited. + +### C4 — Source-generated JSON only + +Runtime reflection-based JSON serialization is disallowed. JSON paths must use source-generated `System.Text.Json` contexts. + +### C5 — Tests run in parallel and must avoid process-global mutation + +Test projects run in parallel by default. Tests must not mutate process-global state unless explicitly isolated with collection-level opt-outs. + +### C6 — Commands must never call Environment.Exit + +Command implementations return exit codes/results through framework abstractions and must not terminate the process directly. + +### C7 — Schema v1 is append-only + +For versioned machine-readable contracts, `v1` schemas are append-only. Existing fields/semantics are not removed or redefined. + +### C8 — Zero warnings + +Warnings are treated as errors. Builds and CI must remain warning-free. + +## IV. Testing Tiers + +- `Terminal.Gui.Cli.Tests` — unit tests +- `Terminal.Gui.Cli.IntegrationTests` — integration tests +- `Terminal.Gui.Cli.SmokeTests` — smoke-level validation + +## V. Governance + +Constitution changes require a pull request that updates this file and explains the rationale and migration impact. diff --git a/src/Terminal.Gui.Cli/Properties/AssemblyInfo.cs b/src/Terminal.Gui.Cli/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8f9e0d5 --- /dev/null +++ b/src/Terminal.Gui.Cli/Properties/AssemblyInfo.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo ("Terminal.Gui.Cli.Tests")] +[assembly: InternalsVisibleTo ("Terminal.Gui.Cli.IntegrationTests")] +[assembly: InternalsVisibleTo ("Terminal.Gui.Cli.SmokeTests")] diff --git a/src/Terminal.Gui.Cli/Terminal.Gui.Cli.csproj b/src/Terminal.Gui.Cli/Terminal.Gui.Cli.csproj new file mode 100644 index 0000000..62b85da --- /dev/null +++ b/src/Terminal.Gui.Cli/Terminal.Gui.Cli.csproj @@ -0,0 +1,18 @@ + + + + Terminal.Gui.Cli + Terminal.Gui.Cli + true + + Terminal.Gui.Cli + CLI helpers and abstractions for Terminal.Gui applications. + terminal-gui;tui;cli + README.md + + + + + + + diff --git a/tests/Terminal.Gui.Cli.IntegrationTests/Terminal.Gui.Cli.IntegrationTests.csproj b/tests/Terminal.Gui.Cli.IntegrationTests/Terminal.Gui.Cli.IntegrationTests.csproj new file mode 100644 index 0000000..7dd9131 --- /dev/null +++ b/tests/Terminal.Gui.Cli.IntegrationTests/Terminal.Gui.Cli.IntegrationTests.csproj @@ -0,0 +1,18 @@ + + + + Exe + Terminal.Gui.Cli.IntegrationTests + false + true + + + + + + + + + + + diff --git a/tests/Terminal.Gui.Cli.SmokeTests/Terminal.Gui.Cli.SmokeTests.csproj b/tests/Terminal.Gui.Cli.SmokeTests/Terminal.Gui.Cli.SmokeTests.csproj new file mode 100644 index 0000000..d8b6008 --- /dev/null +++ b/tests/Terminal.Gui.Cli.SmokeTests/Terminal.Gui.Cli.SmokeTests.csproj @@ -0,0 +1,18 @@ + + + + Exe + Terminal.Gui.Cli.SmokeTests + false + true + + + + + + + + + + + diff --git a/tests/Terminal.Gui.Cli.Tests/Terminal.Gui.Cli.Tests.csproj b/tests/Terminal.Gui.Cli.Tests/Terminal.Gui.Cli.Tests.csproj new file mode 100644 index 0000000..6fd2fea --- /dev/null +++ b/tests/Terminal.Gui.Cli.Tests/Terminal.Gui.Cli.Tests.csproj @@ -0,0 +1,18 @@ + + + + Exe + Terminal.Gui.Cli.Tests + false + true + + + + + + + + + + +