diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..c7f72d4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# This file has NO effect on required reviews today -- "Require review from +# Code Owners" is intentionally left OFF in branch protection (see +# docs/superpowers/specs/2026-09-05-oss-repo-governance-hardening-design.md, +# "Out of Scope": a single-maintainer repo can't satisfy independent +# code-owner review, so enabling this would either block merges or produce +# meaningless self-review). +# +# This file exists so that turning required review on later (once a second +# qualified maintainer exists, per project-roadmap.md's "Public and +# enterprise governance" section) is a one-line branch-protection change, +# not a design task done under time pressure. + +* @SHcommit diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 2c4e92b..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Bug report -about: Report a defect in ADR Toolkit -title: "" -labels: bug ---- - -## Summary - -## Steps to Reproduce - -1. -2. -3. - -## Expected Behavior - -## Actual Behavior - -## Environment - -- Harness: Claude Code / Codex CLI / Gemini CLI / Antigravity CLI / generic / other -- ADR Toolkit version (`skills/adr-toolkit/VERSION`): -- OS: - -## Relevant Output - -Paste the command and its output, including `--json` output where -applicable. - -``` -``` - -## Scope - -- [ ] This affects `adr.py validate`, `create`, `index`, `check`, `graph`, - or `search`. -- [ ] This affects a specific harness adapter (Codex, Gemini CLI, - Antigravity, or generic). diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..63b8d73 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,80 @@ +name: Bug Report +description: Report something that isn't working as expected +title: "[Bug]: " +labels: ["needs-triage", "type:bug"] +body: + - type: input + id: version + attributes: + label: Version + description: "Output of `adr --version` or the release tag you're on" + placeholder: "v1.0.1" + validations: + required: true + - type: dropdown + id: os + attributes: + label: Operating System + options: + - Linux + - macOS + - Windows + - Other (specify in Additional context) + validations: + required: true + - type: dropdown + id: environment + attributes: + label: Environment + description: How you're running ADR Toolkit + options: + - Claude Code + - Codex CLI + - Gemini CLI + - Antigravity CLI + - Cline CLI + - Direct CLI (pip install / python script) + - Other (specify in Additional context) + validations: + required: true + - type: input + id: component + attributes: + label: Affected component / adapter + description: "e.g. `adr.py check`, `adapters/codex`, ADR index generation" + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + label: Actual behavior + validations: + required: true + - type: textarea + id: repro + attributes: + label: Reproduction steps + description: Exact commands, in order + render: shell + validations: + required: true + - type: textarea + id: logs + attributes: + label: Logs + description: "Output of the failing command with `--verbose` or `--debug` if available" + render: shell + validations: + required: false + - type: textarea + id: additional + attributes: + label: Additional context + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e9bb723 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/SHcommit/ADR-toolkit/security/policy + about: Please report vulnerabilities privately per SECURITY.md, not as a public issue. + - name: Question / discussion + url: https://github.com/SHcommit/ADR-toolkit/discussions + about: For open-ended questions that aren't a bug or a concrete feature proposal. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 4f7f064..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature request -about: Propose new functionality for ADR Toolkit -title: "" -labels: enhancement ---- - -## Problem - -What are you trying to do that ADR Toolkit does not support today? - -## Proposed Solution - -## Alternatives Considered - -## Additional Context - -- [ ] I checked `project-roadmap.md` and `improvements.md` for existing - plans that already cover this. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..c3549a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,54 @@ +name: Feature Request +description: Propose a new capability or improvement +title: "[Feature]: " +labels: ["needs-triage", "type:feature"] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What can't you do today, or what's needlessly painful? + validations: + required: true + - type: textarea + id: use-case + attributes: + label: Use case + description: The concrete scenario where this problem shows up + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + validations: + required: false + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Core / CLI (scripts, adr.py) + - Adapter (Claude / Codex / Gemini / Antigravity) + - GitHub automation (.github/**) + - Documentation + - Other + validations: + required: true + - type: dropdown + id: contribution + attributes: + label: Contribution willingness + description: Would you be willing to open a PR for this yourself? + options: + - "Yes, I'd like to implement this" + - "Maybe, with guidance" + - "No, just proposing the idea" + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..aac4144 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + target-branch: "develop" + schedule: + interval: "weekly" + groups: + dev-dependencies: + patterns: + - "*" + open-pull-requests-limit: 5 + + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "develop" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + open-pull-requests-limit: 5 diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..b96970c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,41 @@ +"area:core": + - changed-files: + - any-glob-to-any-file: + - "skills/adr-toolkit/scripts/core/**" + - "skills/adr-toolkit/scripts/evidence/**" + - "skills/adr-toolkit/scripts/rules/**" + - "scripts/**" + +"area:cli": + - changed-files: + - any-glob-to-any-file: + - "skills/adr-toolkit/scripts/adr.py" + - "skills/adr-toolkit/scripts/commands/**" + +"area:adapter": + - changed-files: + - any-glob-to-any-file: + - "adapters/**" + - ".claude-plugin/**" + +"area:github": + - changed-files: + - any-glob-to-any-file: + - ".github/**" + +"area:docs": + - changed-files: + - any-glob-to-any-file: + - "docs/**" + - "*.md" + +"type:test": + - changed-files: + - any-glob-to-any-file: + - "tests/**" + +"type:docs": + - changed-files: + - any-glob-to-any-file: + - "docs/**" + - "*.md" diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..4c77684 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,94 @@ +# Label taxonomy for ADR Toolkit. Synced to the live repo by +# .github/workflows/labels.yml via EndBug/label-sync. Add or edit labels +# here, not in the GitHub UI — UI-only edits are overwritten on the next +# sync run. + +# --- type: what kind of change/report --- +- name: "type:bug" + color: "d73a4a" + description: "Something isn't working" +- name: "type:feature" + color: "a2eeef" + description: "New feature or request" +- name: "type:docs" + color: "0075ca" + description: "Documentation only" +- name: "type:refactor" + color: "cfd3d7" + description: "Code change with no behavior change" +- name: "type:test" + color: "bfdadc" + description: "Test-only change" +- name: "type:epic" + color: "3fb950" + description: "Umbrella/tracking issue that groups related work" + +# --- area: which part of the repo --- +- name: "area:core" + color: "5319e7" + description: "skills/adr-toolkit/scripts core and CLI commands" +- name: "area:cli" + color: "5319e7" + description: "adr.py entrypoint and command wiring" +- name: "area:adapter" + color: "5319e7" + description: "adapters/** and .claude-plugin/** harness integrations" +- name: "area:github" + color: "5319e7" + description: ".github/** workflows, templates, and repo automation" +- name: "area:docs" + color: "5319e7" + description: "docs/**, README, CONTRIBUTING, and other project documentation" + +# --- priority --- +- name: "priority:P0" + color: "b60205" + description: "Drop everything -- breaks release, security, or data integrity" +- name: "priority:P1" + color: "d93f0b" + description: "Should land in the next release" +- name: "priority:P2" + color: "fbca04" + description: "Normal priority, no target release yet" + +# --- workflow state --- +- name: "needs-triage" + color: "ededed" + description: "Not yet reviewed by a maintainer" +- name: "blocked" + color: "000000" + description: "Waiting on an external dependency or decision" + +# --- size (applied manually at review time, not automated in this plan) --- +- name: "size:XS" + color: "c2e0c6" + description: "Trivial change" +- name: "size:S" + color: "c2e0c6" + description: "Small, single-concern change" +- name: "size:M" + color: "fef2c0" + description: "Moderate change, multiple files" +- name: "size:L" + color: "f9d0c4" + description: "Large change, review carefully" +- name: "size:XL" + color: "e99695" + description: "Very large -- consider splitting" + +# --- kept from GitHub's defaults: still useful, not replaced --- +- name: "good first issue" + color: "7057ff" + description: "Good for newcomers" +- name: "help wanted" + color: "008672" + description: "Extra attention is needed" +- name: "duplicate" + color: "cfd3d7" + description: "This issue or pull request already exists" +- name: "invalid" + color: "e4e669" + description: "This doesn't seem right" +- name: "wontfix" + color: "ffffff" + description: "This will not be worked on" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..114baac --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,39 @@ +name: labeler + +on: + pull_request_target: + types: [opened, synchronize, reopened] + issues: + types: [opened] + +permissions: + contents: read + +jobs: + label-pr: + if: github.event_name == 'pull_request_target' + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v7 + with: + configuration-path: .github/labeler.yml + sync-labels: false + + triage-issue: + if: github.event_name == 'issues' + permissions: + issues: write + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: ["needs-triage"], + }); diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 0000000..c5e2640 --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,21 @@ +name: sync-labels + +on: + push: + branches: [develop] + paths: [".github/labels.yml"] + workflow_dispatch: {} + +permissions: + contents: read + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EndBug/label-sync@52074158190acb45f3077f9099fea818aa43f97a # v2.3.3 + with: + config-file: .github/labels.yml + delete-other-labels: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cac8552..f8ca0d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,12 +27,12 @@ jobs: id-token: write attestations: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.12" - name: Install dependencies - run: pip install pytest build + run: pip install -e ".[dev]" - name: Run tests run: python -m pytest tests/unit tests/integration -v - name: Check manifest versions are in sync @@ -54,11 +54,11 @@ jobs: run: python -m build - name: Generate build provenance attestation if: ${{ !github.event.repository.private }} - uses: actions/attest-build-provenance@v2 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2 with: subject-path: ${{ steps.package.outputs.archive }} - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: generate_release_notes: true files: | @@ -68,9 +68,8 @@ jobs: dist/*.tar.gz - name: Publish Python Package to PyPI if: ${{ !github.event.repository.private }} - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 continue-on-error: true with: skip-existing: true - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9de48b3..664ef19 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,17 +19,14 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.12"] - exclude: - - os: macos-latest - python-version: "3.9" + python-version: ["3.10", "3.12"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies - run: pip install pytest pytest-cov + run: pip install -e ".[dev]" - name: Run tests run: python -m pytest tests/unit tests/integration -v --cov=skills/adr-toolkit/scripts --cov-branch --cov-report=term-missing --cov-fail-under=85 @@ -41,7 +38,7 @@ jobs: with: python-version: "3.12" - name: Install mypy - run: pip install mypy + run: pip install -e ".[dev]" - name: Type-check the fully-typed core modules run: >- mypy @@ -50,6 +47,43 @@ jobs: skills/adr-toolkit/scripts/core/contracts.py --strict + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install ruff + run: pip install -e ".[dev]" + - name: Lint (E9 + F -- syntax errors and pyflakes only for now) + run: ruff check . + + dependency-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Upgrade pip (fixes known pip CVEs before auditing anything else) + run: python -m pip install --upgrade pip + - name: Export pinned dev requirements + run: python scripts/export_dev_requirements.py > "$RUNNER_TEMP/adr-dev-requirements.txt" + - name: Install pinned dev tools (not the project itself) + # Deliberately skips `pip install -e .`: adr-toolkit isn't + # audited as a third-party dependency, + # so pip-audit can never resolve it there, and --skip-editable + # can't be combined with --strict (pip-audit treats every skipped + # dependency as fatal under --strict, unconditionally -- see + # pip_audit/_cli.py). This job audits the pinned dev toolchain, + # not this project's own unpublished package. + run: pip install -r "$RUNNER_TEMP/adr-dev-requirements.txt" + - name: Install pip-audit + run: pip install pip-audit==2.9.0 + - name: Audit installed dev dependencies for known vulnerabilities + run: pip-audit --strict + version-drift: runs-on: ubuntu-latest steps: @@ -75,8 +109,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Validate PR Title Conventional Commits Format + env: + PR_TITLE: ${{ github.event.pull_request.title }} run: | - TITLE="${{ github.event.pull_request.title }}" + TITLE="$PR_TITLE" echo "Checking PR Title: $TITLE" REGEX="^(feat|fix|docs|style|refactor|perf|test|chore|ci)(\([a-z0-9-]+\))?!?: .+" if [[ ! "$TITLE" =~ $REGEX ]]; then @@ -107,8 +143,18 @@ jobs: run: npm install -g @openai/codex@0.151.0 - name: Install Gemini CLI run: npm install -g @google/gemini-cli@0.46.0 - - name: Install Antigravity CLI - run: curl -fsSL https://antigravity.google/cli/install.sh | bash + - name: Install pinned Antigravity CLI artifact + run: | + set -euo pipefail + ARCHIVE="$RUNNER_TEMP/antigravity-cli-1.1.27-linux-x64.tar.gz" + curl --proto '=https' --tlsv1.2 -fsSL \ + https://storage.googleapis.com/antigravity-public/antigravity-cli/1.1.27-5211191891591168/linux-x64/cli_linux_x64.tar.gz \ + -o "$ARCHIVE" + printf '%s %s\n' \ + '793d4b9ea2c08d9a7e50bafa02cfc8c19424bd60d6e83f91408d45f9c6d4ce79a5d576fede5bef164d823abf84f81359a14b4ca665952c47b0a7cfd743bb69c0' \ + "$ARCHIVE" | sha512sum --check - + tar -xzf "$ARCHIVE" -C "$RUNNER_TEMP" antigravity + install -D -m 0755 "$RUNNER_TEMP/antigravity" "$HOME/.local/bin/agy" - name: Verify Codex CLI adapter end to end run: | set -euo pipefail diff --git a/SECURITY.md b/SECURITY.md index d4d34a4..fe55430 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,9 @@ Security fixes target the latest released version of ADR Toolkit. ## Reporting a Vulnerability -Please report security issues privately to the repository owner before opening a -public issue. Include: +Please use GitHub's private vulnerability reporting form at + instead of +opening a public issue. Include: - affected version or commit - reproduction steps @@ -29,13 +30,10 @@ signature -- no private key is held or rotated by this project -- proving the archive was produced by this repository's own CI, from the exact commit the release tag points to. -**While this repository is private, attestation is not generated** -- -GitHub's attestation API rejects it for a user-owned private repository. -The release workflow skips that step automatically and still publishes -the archive and its checksum; attestation starts appearing on releases -once the repository goes public, with no workflow change required. Check -a given release's assets on the Releases page to see whether an -attestation is available for it. +The repository has been public since 2026-08-29, so current releases are +eligible for attestation. The workflow keeps a private-repository guard only +so forks or future visibility changes fail safely; check a given release's +assets to confirm that its attestation was actually published. To verify a downloaded archive: @@ -45,7 +43,6 @@ sha256sum -c adr-toolkit-skill-vX.Y.Z.tar.gz.sha256 # Provenance: confirms the archive was actually built by this repo's CI, # not a look-alike release from a compromised account or a different repo. -# Only available once this repository is public -- see the note above. gh attestation verify adr-toolkit-skill-vX.Y.Z.tar.gz -R SHcommit/ADR-toolkit ``` diff --git a/changelog.md b/changelog.md index 9c9fe3a..f55cf9a 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,22 @@ Lightweight human-readable summary of meaningful repository changes. ## Unreleased +- Added scalable GitHub governance: structured Issue Forms, source-controlled + label taxonomy, path-based PR labels, new-issue triage, weekly grouped + Dependabot updates targeting `develop`, and a dormant CODEOWNERS draft. +- Added ruff and strict dependency-audit CI gates, upgraded the supported + Python floor to 3.10, and added regression tests for repository governance + configuration. +- Hardened CI supply chain behavior: replaced the Antigravity `curl | bash` + installer with a versioned SHA-512-verified artifact, pinned release Actions + to commit SHAs, pinned release build tooling, and removed PR-title shell + expression injection. +- Corrected the governance audit to recognize the already-active repository + branch/tag rulesets through the ruleset APIs rather than treating a classic + branch-protection 404 as proof of no protection. +- Enabled live Discussions, dependency security updates and alerts, secret + scanning with push protection, private vulnerability reporting, and + automatic deletion of merged branches. - Added a Cline CLI adapter (`adapters/cline/`) so Cline CLI and ClinePass users can install the `skills/adr-toolkit` package. Cline installs skills through the open Agent Skills standard (SKILL.md), so the adapter is README-only — no diff --git a/docs/enterprise-adoption.md b/docs/enterprise-adoption.md index 0086ac5..e21e9c7 100644 --- a/docs/enterprise-adoption.md +++ b/docs/enterprise-adoption.md @@ -23,11 +23,16 @@ ADR Toolkit의 강점은 AI가 초안과 언어 표현을 돕더라도 repositor ### 확인된 사실 -- 저장소는 private이며 `master`와 `develop` 보호 규칙 및 repository ruleset이 없다. -- 현재 플랜에서는 private 저장소 protection API가 활성화되지 않는다. +- 저장소는 2026-08-29부터 public이며, repository ruleset API 재확인 결과 + `master`/`develop`/`release/*` branch와 `v*` tag 보호 규칙이 2026-09-02부터 + active다 (ruleset IDs `22101891`, `22102322`). +- classic branch-protection API의 404는 ruleset 부재를 의미하지 않는다. 실제 + 적용 여부는 `repos/SHcommit/ADR-toolkit/rules/branches/`와 + `repos/SHcommit/ADR-toolkit/rulesets/`로 검증한다. - CI는 Ubuntu, macOS, Windows와 지원 Python 조합에서 동작한다. - 첫 PR은 모든 CI를 통과했지만 독립 review 없이 병합됐다. -- CODEOWNERS와 PR template이 없다. +- PR template은 있지만, CODEOWNERS는 활성화되지 않은 dormant draft뿐이다 + (독립 승인 가능한 qualified maintainer가 2명 이상이 되기 전까지 미활성). - 코어는 locale, ID, lifecycle, schema, relationship, index, CHECK의 repository state를 결정론적으로 검증한다. - 중앙 서비스, 사용자 계정, 조직 RBAC, audit export, telemetry는 없다. @@ -62,8 +67,12 @@ CI 통과는 구조화된 정책의 증거일 뿐, 결정의 사업적 타당성 ## 4. 공개 저장소 전환 Gate -사용자가 계획한 public 전환 직후 다음 repository ruleset을 적용하고 API로 실제 -상태를 재조회한다. +저장소는 2026-08-29부터 public이다. 아래 repository ruleset은 2026-09-02에 +적용됐고 2026-09-06에 ruleset API와 effective-rules API로 재검증했다. Branch +ruleset `22101891`은 PR, required checks, conversation resolution, +force-push/deletion 차단을 적용하며 tag ruleset `22102322`는 `v*` 수정·삭제를 +차단한다. 단, CI matrix가 Python 3.9에서 3.10으로 바뀌므로 이 변경을 merge한 +직후 required-check context도 함께 갱신해야 한다. | 대상 | 권장 통제 | 도입 이유 | | --- | --- | --- | @@ -167,8 +176,11 @@ Toolkit 자체에 계정 시스템을 서둘러 넣기보다 GitHub의 인증· 1. ~~ADR directory 로딩을 공통 iterator로 통합해~~ **완료 (2026-08-30).** `core.adr_directory.iter_adr_files`로 validate/index/related/CHECK를 통합했고 각 명령의 warning 의미는 그대로 유지했다. -2. public 전환 시 PR template과 CONTRIBUTING/SECURITY 문서를 만들고 ruleset 적용을 - 자동 검증한다. **저장소가 아직 private이라 시작 조건이 충족되지 않았다.** +2. ~~PR template과 CONTRIBUTING/SECURITY 문서를 만들고 ruleset 적용을 자동 + 검증한다.~~ **완료 (2026-09-06 재검증).** PR template, + CONTRIBUTING/SECURITY가 존재하고 branch/tag ruleset의 effective rules를 API로 + 확인했다. CI job 이름이 바뀌면 required-check context도 같은 변경의 rollout + 절차에서 갱신한다. 3. ~~CHECK 결과를 `VERIFIED`, `VIOLATED`, `NOT_APPLICABLE`, `UNVERIFIABLE`의 안정된 machine-readable contract로 승격한다.~~ **완료 (2026-08-30).** 모든 finding이 기존 `kind` 값과 별개로 `confidence` 필드를 직접 갖는다 @@ -202,7 +214,7 @@ Toolkit 자체에 계정 시스템을 서둘러 넣기보다 GitHub의 인증· | 단계 | 완료 조건 | | --- | --- | | v0.2.0 | P0 전체 통과, 최종 PR CI, 승인된 version bump와 release 절차 | -| Public | `master`/`develop`/`v*` ruleset API 검증, PR template, CONTRIBUTING, SECURITY | +| Public | `master`/`develop`/`release/*`/`v*` ruleset API 검증 (완료, 2026-09-06), PR template (완료), CONTRIBUTING (완료), SECURITY 및 private vulnerability reporting (완료) | | Team | 2명 이상 qualified maintainer, CODEOWNERS 독립 승인, 예외 owner/expiry | | Enterprise | 조직 ruleset·reusable workflow, audit export, taxonomy, 정의된 adoption metrics | | Multi-repo | 반복된 탐색 실패와 운영 요구를 근거로 registry/portal 도입 | diff --git a/docs/oss-repository-governance-audit.md b/docs/oss-repository-governance-audit.md new file mode 100644 index 0000000..b50aea2 --- /dev/null +++ b/docs/oss-repository-governance-audit.md @@ -0,0 +1,225 @@ +# OSS Repository Governance Audit + +검증 기준일: 2026-09-06 (Asia/Seoul) + +이 보고서는 파일 존재 여부뿐 아니라 backlog가 수백~수천 건으로 늘어났을 때 +한 명의 maintainer가 반복 분류·보안 점검·merge 통제를 감당할 수 있는지를 +기준으로 작성했다. 로컬 브랜치의 구성과 GitHub API로 조회한 live 설정을 +구분한다. + +## 1. Executive Summary + +ADR Toolkit은 문서, 테스트, release provenance, 다중 플랫폼 CI가 이미 강한 +소형 OSS 저장소다. 이번 hardening으로 label taxonomy, path labeler, +Dependabot, Issue Forms, lint, dependency audit, dormant CODEOWNERS를 추가했고, +live repository에는 Discussions, 보안 알림, 자동 security update, secret +scanning/push protection, private vulnerability reporting을 활성화했다. + +가장 중요한 리뷰 결과는 기존 감사의 전제 오류다. Classic branch-protection +API가 404를 반환했지만 repository ruleset은 2026-09-02부터 active였다. +Ruleset `22101891`은 `master`/`develop`/`release/*`에 PR, required checks, +conversation resolution, deletion/non-fast-forward 차단을 적용하고, ruleset +`22102322`는 `v*` tag 수정·삭제를 차단한다. 실제 남은 위험은 보호 규칙의 +부재가 아니라 CI matrix 변경 후 required-check 이름이 drift하는 것이다. + +현재 운영 수준은 **🟢 성장 가능한 기본 구조, 단 rollout 1단계 필요**로 +평가한다. 이 브랜치를 `develop`에 merge한 직후 ruleset에서 Python 3.9 check를 +3.10 check로 교체하고 `lint`/`dependency-audit`를 required로 추가해야 한다. + +## 2. Repository Audit + +| 영역 | 현재 상태 | 문제 또는 근거 | 우선순위 | +| --- | --- | --- | --- | +| README | 🟢 충분함 | 설치, 기능, scope, 기여 링크 제공 | P2 | +| CONTRIBUTING | 🟢 충분함 | Git Flow, local checks, ADR/adapter 규칙 제공 | P2 | +| CODE_OF_CONDUCT | 🟢 충분함 | 행동 기준·신고·집행 범위 명시 | P2 | +| SECURITY | 🟢 충분함 | private advisory 경로, 지원 버전, checksum/attestation 검증 제공 | P1 | +| LICENSE | 🟢 충분함 | MIT license 존재 | P2 | +| CHANGELOG | 🟢 충분함 | `Unreleased`와 release별 human-readable 기록 | P2 | +| Roadmap | 🟢 충분함 | trigger 기반 deferred work와 완료 항목 구분 | P2 | +| ADR / architecture docs | 🟢 충분함 | `docs/decisions/`와 설계·감사 문서 존재 | P2 | +| Issue templates | 🟢 충분함 | bug/feature Issue Forms와 blank issue 차단 | P1 | +| PR template | 🟢 충분함 | Conventional title, ADR/example 영향 확인 | P1 | +| Labels | 🟢 충분함 | 25개 source taxonomy 중 type/area/priority/size/workflow 20개를 live sync 완료 | P1 | +| GitHub Projects | 🟡 개선 필요 | 기능은 enabled지만 Project 없음; open issue 0개라 지금 생성은 과함 | NEXT | +| Milestones | 🟡 개선 필요 | milestone 없음; release backlog가 생길 때 도입 | NEXT | +| CODEOWNERS | 🟡 개선 필요 | dormant draft 존재, 1인 maintainer라 required review는 의도적으로 OFF | NEXT | +| Discussions | 🟢 충분함 | live 활성화 및 Q&A category 확인 | P1 | +| Branch/tag ruleset | 🟡 개선 필요 | active/effective 확인; required checks는 아직 Python 3.9 이름을 포함 | P0 rollout | +| Merge 정책 | 🟢 충분함 | PR 강제와 merge 후 short-lived branch 자동 삭제 활성화 | P1 | +| Build | 🟢 충분함 | release에서 wheel/sdist와 skill archive 생성 | P1 | +| Test | 🟢 충분함 | unit+integration 550+ cases | P1 | +| Lint | 🟢 충분함 | ruff E9/F fast gate 추가 | P1 | +| Type check | 🟢 충분함 | 핵심 typed module에 mypy `--strict` | P1 | +| Coverage | 🟢 충분함 | branch coverage 85% floor | P1 | +| Multi-platform | 🟢 충분함 | Linux/macOS/Windows × Python 3.10/3.12 | P1 | +| E2E | 🟢 충분함 | Codex/Gemini/Antigravity adapter install-and-run | P1 | +| Release | 🟡 개선 필요 | direct-tag workflow와 version gate 있음; PyPI publish는 `continue-on-error` | P1 | +| Artifact verification | 🟢 충분함 | SHA-256 release asset과 version/tag drift gate | P1 | +| Provenance / attestation | 🟢 충분함 | public release에 GitHub Artifact Attestation | P1 | +| Changelog requirement | 🟡 개선 필요 | template/process는 있으나 자동 path-based gate 없음 | NEXT | +| Documentation drift | 🟢 충분함 | examples execution/drift gate 제공 | P1 | +| Dependabot | 🟢 충분함 | pip/actions weekly grouped PR, target `develop` | P1 | +| GitHub Actions pinning | 🟡 개선 필요 | write-capable release actions는 SHA pin; read-only CI first-party actions는 major pin | P2 | +| Remote installer | 🟢 충분함 | Antigravity fixed artifact + official SHA-512 verification, `curl | bash` 제거 | P0 | +| Package lock | 🟡 개선 필요 | runtime dependency 0개; dev tools exact pin이라 lock 부재 위험은 제한적 | P2 | +| Dependency/security audit | 🟢 충분함 | `pip-audit --strict`, alerts/security updates/secret scanning enabled | P1 | + +## 3. NOW + +- 완료: `.github/labels.yml`과 live label taxonomy 동기화. +- 완료: changed path 기반 PR labeler와 new issue `needs-triage` 적용. +- 완료: structured Issue Forms와 Discussions/Q&A 경로. +- 완료: pip/GitHub Actions Dependabot을 weekly grouped update로 구성하고 + `develop`을 target branch로 고정. +- 완료: ruff lint와 `pip-audit --strict` CI gate. +- 완료: Antigravity CI installer를 versioned artifact + SHA-512 검증으로 교체. +- 완료: release workflow dependency pin 및 write-capable workflow Action SHA pin. +- 완료: vulnerability alerts, automated security fixes, secret scanning, + push protection, private vulnerability reporting 활성화. +- rollout 직후: ruleset required contexts에서 Python 3.9 두 개를 제거하고 + Python 3.10 세 개, `lint`, `dependency-audit`를 추가. + +## 4. NEXT + +- Issue가 약 20~30개 이상 지속되거나 동시에 진행 중인 work item이 10개를 + 넘으면 Project를 만든다. +- 2명 이상의 qualified maintainer가 생기면 dormant CODEOWNERS를 영역별로 + 분리하고 code-owner review를 required로 전환한다. +- PR queue가 10개 이상 지속될 때 `size:XS`~`size:XL` 자동 분류를 추가한다. +- release 목표 issue가 5개 이상 모이면 milestone을 source of truth로 쓴다. +- ruleset check-context drift가 다시 발생하면 settings verification script나 + Terraform/GitHub provider 기반 ruleset-as-code를 도입한다. + +권장 수동 size 기준은 XS 10 LOC 이하, S 50 이하, M 200 이하, L 500 이하, +XL 500 초과다. Generated file, security-sensitive workflow, schema 변경은 LOC와 +무관하게 한 단계 상향할 수 있다. + +## 5. LATER + +- stale bot: abandoned backlog가 실제로 누적된 뒤에만 도입한다. 기준은 + 90일 inactive → stale, 추가 30일 → close이며 `security`, `pinned`, + `roadmap`, `help wanted`, `priority:P0`, `blocked`는 제외한다. +- organization ruleset/reusable workflow/RBAC/audit export: 동일 운영을 + 반복하는 repository가 2개 이상일 때 도입한다. +- ML triage, automatic assignment, 복잡한 release train은 현재 도입하지 않는다. + +## 6. Security / Supply Chain Findings + +1. **해결 — remote code execution:** `curl install.sh | bash`가 실제 PR CI에서 + 실행되고 있었다. 공식 manifest가 가리킨 Antigravity CLI `1.1.27` Linux + artifact URL과 SHA-512를 고정해 다운로드·검증·설치 단계로 분리했다. +2. **해결 — release mutability:** `release.yml`의 Action ref를 현재 commit SHA로 + 고정하고 version comment를 남겼다. Python build/test tool도 dev extra pin을 + 사용한다. +3. **해결 — dependency visibility:** Dependabot security updates와 alerts, + `pip-audit --strict`, secret scanning/push protection을 활성화했다. +4. **해결 — PR title command injection:** untrusted PR title expression을 inline + shell 문자열에 삽입하지 않고 step environment로 전달하도록 변경했다. +5. **남음 — partial release:** PyPI publish가 `continue-on-error: true`라 GitHub + Release 성공과 PyPI 실패가 동시에 가능하다. Trusted Publisher 안정성이 + 확인되면 fail-closed 전환을 검토한다. +6. **수용 — read-only CI Action tags:** 일반 test/labeler의 GitHub-owned Actions는 + major tag를 사용한다. Release처럼 write 권한을 갖는 경로는 SHA pin을 적용했다. + +## 7. 추가/수정 파일 + +```text +.github/ +├── CODEOWNERS +├── dependabot.yml +├── labeler.yml +├── labels.yml +├── ISSUE_TEMPLATE/ +│ ├── bug_report.yml +│ ├── feature_request.yml +│ └── config.yml +└── workflows/ + ├── labeler.yml + ├── labels.yml + ├── release.yml + └── test.yml +scripts/ +└── export_dev_requirements.py +tests/unit/ +└── test_github_governance.py +``` + +각 파일의 실행 가능한 실제 내용은 위 경로 자체가 source of truth다. 추가로 +`pyproject.toml`, `SECURITY.md`, `project-roadmap.md`, +`docs/enterprise-adoption.md`, 본 설계 문서를 현재 운영 상태에 맞게 수정했다. + +## 8. GitHub UI 설정 + +API로 확인/적용한 live 상태: + +- Discussions: enabled. +- Repository Projects: enabled, Project instance는 없음. +- Dependabot security updates: enabled. +- Vulnerability alerts: enabled. +- Secret scanning / push protection: enabled. +- Private vulnerability reporting: enabled. +- Delete branch on merge: enabled. +- Branch ruleset `22101891`: active, bypass actor 없음. +- Tag ruleset `22102322`: active, bypass actor 없음. + +이 브랜치 merge 직후 Settings → Rules → Rulesets에서 `protected-branches`의 +required checks를 다음과 같이 맞춘다. + +```text +remove: pytest (ubuntu-latest, 3.9) +remove: pytest (windows-latest, 3.9) +add: pytest (ubuntu-latest, 3.10) +add: pytest (macos-latest, 3.10) +add: pytest (windows-latest, 3.10) +add: lint +add: dependency-audit +keep: all Python 3.12 checks, type-check, version-drift, + examples-drift, pr-title-check, harness-parity +``` + +Required approving review count와 code-owner review는 maintainer가 1명인 동안 +0/OFF를 유지한다. Signed commits도 현재는 요구하지 않는다. + +## 9. Issue Backlog 후보 + +| 후보 | 출처 | 추천 labels | 크기 | +| --- | --- | --- | --- | +| `adapters/README.md`에 “새 harness adapter 추가하기” tutorial 작성 | `docs/adr-toolkit-audit-report.md` §2.6 | `type:docs`, `area:adapter`, `good first issue` | S | +| harness parity를 `check/search/graph/create`까지 확장 | `project-roadmap.md` Harness parity | `type:test`, `area:adapter`, `help wanted` | M | +| 실제 사용자 피드백 후 Traditional Chinese `zh-TW` catalog 검토 | `project-roadmap.md` Internationalization | `type:feature`, `area:core`, `help wanted` | M | +| Accepted ADR metadata factual-correction policy 설계 | `project-roadmap.md` Lifecycle research | `type:docs`, `area:docs`, `priority:P2` | M | +| ruleset required-check drift 자동 검증 | 이번 감사의 재발 방지 항목 | `type:test`, `area:github`, `priority:P1` | S | + +첫 good-first issue는 문서 tutorial이 가장 적합하다. Product behavior를 바꾸지 +않고 기존 adapter를 비교해 명확한 acceptance criteria를 만들 수 있다. + +## 10. 최종 목표 구조 + +```text +Idea + ↓ +Issue Form / Discussion + ↓ +Auto Triage / Label + ↓ +Issue backlog (Project는 규모 trigger 충족 시) + ↓ +Contributor + ↓ +Pull Request + ↓ +Automated Quality Gates + ↓ +Review + conversation resolution + ↓ +Merge to develop + ↓ +Release branch → master → v* tag + ↓ +Attestation / checksum / dependency & security maintenance +``` + +목표는 자동화 개수가 아니라 maintainer 판단이 필요한 우선순위·설계·review만 +사람에게 남기고, 입력 구조화·경로 분류·반복 검증·dependency 감시는 GitHub와 +CI가 수행하게 하는 것이다. diff --git a/docs/superpowers/specs/2026-09-05-oss-repo-governance-hardening-design.md b/docs/superpowers/specs/2026-09-05-oss-repo-governance-hardening-design.md new file mode 100644 index 0000000..7816049 --- /dev/null +++ b/docs/superpowers/specs/2026-09-05-oss-repo-governance-hardening-design.md @@ -0,0 +1,259 @@ +# OSS Repository Governance & Hardening Design + +## Scope + +Make ADR Toolkit's GitHub operations (issue/PR triage, labels, dependency +updates, CI gates, branch protection, contributor onboarding) hold up if +issues and PRs grow from single digits into the hundreds, without adding +automation the current one-maintainer, one-repository reality doesn't need +yet. Covers `.github/**` configuration, CI workflow structure, and the +GitHub-side settings that can't live in code. Does not change ADR Toolkit's +product code, CLI behavior, or `docs/decisions/` governance model. + +## Context — audit findings as of 2026-09-05 + +> **2026-09-06 review correction:** the original audit queried only the +> classic branch-protection endpoint. A 404 from that endpoint does not prove +> repository rulesets are absent. Ruleset and effective-rules APIs show that +> active branch/tag rulesets have existed since 2026-09-02 (IDs `22101891` +> and `22102322`). The original audit also incorrectly described the +> Antigravity adapter as manual-only even though `harness-parity` executed its +> remote bootstrapper. The decisions below are corrected accordingly. + +Re-verified against the live repository (`gh api`/`gh repo view`), not just +file presence, because `docs/enterprise-adoption.md` §4/§8 described this repo +as still private with a single precondition ("저장소 public 전환") blocking the +public-readiness gate. That precondition had already been met and the document +was stale: + +- The repository has been **public since 2026-08-29**, and `master` has + already shipped `v1.0.0` and `v1.0.1` (PyPI publishing pipeline, antigravity + harness-parity CI, a Windows lock-metadata fix, mypy `--strict` fix). +- Active repository rulesets protect `master`, `develop`, `release/*`, and + `v*` tags. They require PRs, status checks, conversation resolution, and + prohibit deletion/non-fast-forward updates without a bypass actor. The + remaining rollout concern is keeping required-check contexts synchronized + with CI matrix changes (this branch replaces Python 3.9 with 3.10). +- `develop` was frozen at the `v0.3.2` sync point while `master` advanced + through the `v1.0.0`/`v1.0.1` releases — the git-flow-mandated + release-branch back-merge into `develop` never happened. **Fixed as a + prerequisite to this spec** via `chore/sync-develop-with-v1.0.1` (PR #17, + not yet merged at spec time) — this branch is based on that synced state. +- No `dependabot.yml`, no `CODEOWNERS`, no path-based auto-labeler. Labels are + the unmodified GitHub defaults (`bug`, `enhancement`, `documentation`, + `good first issue`, `help wanted`, `question`, `duplicate`, `invalid`, + `wontfix`, `accessibility`) — no `type:`/`area:`/`priority:`/`size:` axis at + all, so nothing about a large backlog would currently self-organize. + Issue templates are legacy Markdown (`bug_report.md`, `feature_request.md`), + not structured Issue Forms. +- CI (`.github/workflows/test.yml`) is already more mature than a typical + small OSS repo: multi-OS/multi-Python pytest matrix, a coverage floor + (`--cov-fail-under=85`), a scoped mypy `--strict` job, generated-file drift + checks (`sync_version.py --check`, `verify_examples.py --check`), a + Conventional-Commit PR title gate, and a `harness-parity` job that installs + the real Codex/Gemini CLIs against this repo. There is **no lint job** at + all (no ruff/flake8/black anywhere in the repo) and no dependency/security + scan step. +- `harness-parity` contains one remote-installer pattern the request asked to + scrutinize: `curl -fsSL https://antigravity.google/cli/install.sh | bash`. + This does run in CI before the Antigravity end-to-end adapter verification, + so it must be replaced with a versioned artifact plus checksum verification. +- GitHub Actions are pinned to major-version tags from verified publishers + (`actions/*@v4/@v5`, `softprops/action-gh-release@v2`, + `pypa/gh-action-pypi-publish@release/v1`) — not full commit-SHA pinning, but + not `@latest`/`@master` either. `release.yml` already scopes permissions + per-job (`contents: write`, `id-token: write`, `attestations: write`) and + `test.yml` sets repo-wide `permissions: contents: read`; least-privilege is + already largely in place, not a gap. +- Dependency ecosystems actually in use: Python (`pyproject.toml`) and GitHub + Actions. The only `package.json` is a test fixture; the `npm install -g` + calls in `harness-parity` install pinned global CLIs for testing, not a + project dependency Dependabot should manage. No Docker. +- `project-roadmap.md`'s "Public and enterprise governance" section and + `docs/enterprise-adoption.md` §8 already correctly defer **mandatory** + CODEOWNERS review and organization-wide ruleset/reusable-workflow work + behind explicit preconditions (2+ qualified maintainers; 2+ repositories) + that still aren't met. This spec does not revisit that call — see + Out of Scope. + +## Problem Statement + +The repository's day-to-day contributor mechanics (label triage and dependency +freshness) were never built out because the +project was small and private. It is now public with two releases shipped, +but the operational scaffolding that keeps a growing issue/PR queue navigable +for a single maintainer does not exist yet. Existing rulesets protect release +history, but their required status-check names can drift when CI changes. +Left as-is, the first sign of trouble will be either a permanently blocked PR +after a matrix rename, or a backlog of unlabeled, untriaged work. + +## Solution + +Add the minimum GitHub-native automation that lets issues and PRs +self-organize (path-based labels, a label taxonomy, Dependabot, Issue Forms) +and verify/update the already-active rulesets alongside CI changes, +while explicitly deferring anything that assumes a maintainer team or an +issue volume this repository doesn't have yet (mandatory CODEOWNERS review, +stale-bot, org-wide rulesets). Every "not now" gets a written trigger +condition instead of a vague "later," matching how `project-roadmap.md` and +`docs/enterprise-adoption.md` already record deferred work in this repo. + +## User Stories + +1. As the sole maintainer, I want new issues/PRs to arrive pre-labeled by the + file paths they touch so I don't manually triage every one. +2. As the sole maintainer, I want dependency and Action version bumps to + arrive as routine, reviewable PRs instead of silent drift or a manual audit. +3. As a first-time contributor, I want a structured bug/feature form instead + of a blank Markdown template, so I give the maintainer what's needed on + the first pass. +4. As the sole maintainer, I want `master`/`develop`/`v*` protection verified + through the correct ruleset APIs and required checks kept in sync, without + mandatory independent code-owner review I can't actually staff. +5. As a future contributor, I want a small set of well-labeled "good first + issue" candidates to exist so I know where to start. +6. As the sole maintainer, I want CI to catch missing lint/security-scan + coverage that today has no job at all, without turning every PR into a + slow, heavyweight gate. + +## Decisions + +### NOW — apply in this branch + +- **Path-based auto-labeler**: a labeler config mapping changed-file globs to + `area:*` labels (e.g. `skills/adr-toolkit/**` / `scripts/**` → `area:core`, + `adapters/**` → `area:adapter`, `.github/**` → `area:github`, `docs/**` → + `area:docs`, `tests/**` → `type:test`), run by a workflow triggered on + `pull_request_target` with `contents: read`/`pull-requests: write` only. +- **Label taxonomy**: introduce `type:*`, `area:*`, `priority:*`, `size:*`, + plus `needs-triage` and `blocked`, sized to this repo's actual directories + (`core`/`cli`/`adapter`/`github`/`docs`) rather than the generic list + verbatim. Keep the existing default labels (`bug`, `enhancement`, + `good first issue`, `help wanted`, `documentation`) rather than replacing + them, since GitHub's defaults already cover part of `type:*`/`area:docs` + and dropping them would break any existing issue/PR history referencing + them. +- **Dependabot**: `pip` (root `pyproject.toml`) and `github-actions` + ecosystems only — no `npm`/`docker` entries, since neither has a manifest + Dependabot could act on. Weekly schedule, grouped updates, auto-created + PRs. +- **Issue Forms**: replace the two Markdown templates with structured YAML + forms (bug report: version, OS, environment, affected + component/adapter, expected/actual behavior, repro steps, logs; feature + request: problem, use case, proposed solution, alternatives, affected + area, contribution willingness), plus a `config.yml` pointing to + Discussions/`SECURITY.md` for questions and vulnerabilities instead of a + blank issue. +- **Lint CI job**: add a fast lint job (the repo has no lint tool configured + at all today) to the existing fast-gate jobs in `test.yml`, separate from + the slower `harness-parity` job. +- **Dependency/security scan**: add a lightweight `pip-audit`-style check for + the Python dependency surface as a fast-gate job. +- **Branch protection / ruleset** (GitHub UI/API, not a file in this repo — + see the UI section below): verify the existing active rulesets, then update + required status-check contexts after CI job/matrix changes. They already + require PR + passing checks, block force-push/deletion on protected branches + and `v*`, and require conversation resolution. Do **not** + require code-owner review or signed commits yet — no second qualified + maintainer exists to review against, and no CODEOWNERS file exists to + reference. +- **Update stale docs**: correct `docs/enterprise-adoption.md` §4/§8's + "저장소가 아직 private" framing and `improvements.md`'s already-removed + precondition note now that the public transition and its ruleset gate are + being closed out, so the next reader doesn't re-derive the same stale + premise this session had to correct first. + +### NEXT — once issue/PR volume or contributor count actually grows + +- CODEOWNERS file (drafted but **not** wired to mandatory review) — ready to + flip on once 2+ qualified maintainers exist, per the existing decision in + `docs/enterprise-adoption.md` §4 and `project-roadmap.md`. Do not enable + required review as part of this spec. +- GitHub Projects board with an Inbox → Backlog → Ready → In Progress → + Review → Blocked → Done flow and `Priority`/`Area`/`Size` custom fields, + once there's enough issue volume for a Kanban view to earn its keep over a + flat issue list. +- PR size labeling (`size:XS`…`size:XL`) as a workflow addition once PR + volume makes "the queue is large, which PRs are actually small" a real + question. +- Splitting `test.yml` into an explicit Fast Gate / Integration Gate + workflow pair, once the existing single-workflow job list gets unwieldy + enough that PR authors need the distinction visible rather than just + reading job names. + +### LATER — only once scale actually demands it + +- Stale issue/PR bot. Current issue/PR count doesn't warrant it — explicit + decision **not to adopt now**, matching how this repo already declines + automation it doesn't need (`docs/enterprise-adoption.md` §8 "지금 구현하지 + 않을 것"). Revisit once there's a real backlog of abandoned issues, with + exclusions for `security`, `pinned`, `roadmap`, `help wanted`, + `priority:P0`, and `blocked`. +- Organization-level rulesets, reusable workflows, RBAC, audit export — all + already correctly gated behind "2+ repositories" in `improvements.md`; + this spec doesn't touch that precondition. +- Advanced/ML-based triage bots, automatic contributor assignment, complex + multi-stage release trains. + +## Testing / Validation Decisions + +- Labeler config: validate with a dry-run against recent merged PRs' + changed-file lists to confirm expected labels before enabling the workflow + for real. +- Dependabot: confirm via a manual `dependabot.yml` schema check (GitHub + validates on push) and by watching the first scheduled run produce the + expected grouped PRs. +- Issue Forms: manually preview each form in GitHub's issue-form preview + before merging; forms have no automated test surface. +- New CI jobs (lint, dependency/security scan): must pass on this branch's + own diff and not regress the existing 515 unit tests or `sync_version.py + --check` / `verify_examples.py --check` drift gates. +- Branch protection: query `repos/SHcommit/ADR-toolkit/rulesets/` and + `repos/SHcommit/ADR-toolkit/rules/branches/`. Do not use the classic + `branches//protection` endpoint alone as evidence of ruleset absence. + +## Out of Scope + +- Mandatory CODEOWNERS-backed independent review (no second qualified + maintainer yet — explicit existing decision, not revisited here). +- Organization-level rulesets, reusable workflows, audit export, central + taxonomy (single-repository precondition unmet — explicit existing + decision, not revisited here). +- Stale-bot / auto-triage-bot / auto-assignment automation (volume doesn't + warrant it yet). +- Signed-commit requirements. +- Any change to ADR Toolkit's product code, CLI, or `docs/decisions/` + governance model. +- Full commit-SHA pinning for all third-party Actions (current major-version + tag pinning from verified publishers is a reasonable middle ground for this + repo's risk level; revisit only if a specific tag-mutation incident in one + of the pinned Actions ever demonstrates the gap matters). + +## Open Questions + +- Should PR #17 (the develop/master sync) merge before or independently of + this branch's PR? This branch is based on its tip either way, so either + order is safe, but the maintainer should decide merge order. +- Exact `priority:*` and `size:*` thresholds (e.g. what LOC/file-count counts + as `size:M` vs `size:L`) — left to the maintainer's judgment at + implementation time rather than guessed here. +- Whether `needs-triage` should auto-apply to every new issue/PR via the + labeler workflow, or only to issues (PRs already get `area:*` + + `pr-title-check`) — implementation-time call. + +## Further Notes + +- Rollout order: ruleset verification/check-context sync (GitHub UI/API) → + Dependabot + auto-labeler (lowest + maintenance cost) → Issue Forms → new CI jobs (lint, dependency scan) → + stale-docs correction, so the highest-value, lowest-risk items land first + and the doc correction reflects the final state rather than needing a + second pass. +- The headline review correction is methodological: classic branch protection + and repository rulesets use different APIs. Operational audits must inspect + both, then query effective rules for concrete refs before declaring a gap. +- This spec intentionally does not propose a `.github/labeler.yml` / + `dependabot.yml` schema inline — implementation will write those files + directly against this repo's actual directory names, which is faster to + verify than hand-transcribing YAML into a design doc that could drift from + the real paths by the time it's implemented. diff --git a/handoff.md b/handoff.md index 351e18b..024e44c 100644 --- a/handoff.md +++ b/handoff.md @@ -2,71 +2,55 @@ ## Current task -Completed Production Readiness P1/P2 Backlog Improvements via Parallel -Subagent Execution (Groups A-F); all 550 tests passing. Also added a Cline CLI -adapter (`adapters/cline/`) so Cline CLI and ClinePass users can install the -`skills/adr-toolkit` package — a README-only adapter, verified against Cline CLI -3.0.61 (GitHub issue #19). +OSS repository governance hardening implementation and independent review. +Original SDD Tasks 1–9 are complete; the follow-up review corrected false live +GitHub assumptions and closed additional supply-chain/configuration gaps. -## Scope +## Touched files -- Updated `Agent-toolkit` plugin bundle to v0.3.6. -- Production Readiness Audit completed for `ADR-toolkit` (`analyzing-system`). -- Implemented and verified all High (P1) and Medium (P2) action items: - - Group A: `.adr-toolkit.json` `adr_dir` config & `ADR_DIR`/`ADR_LOCALE` env vars. - - Group B: `SIGINT`/`SIGTERM` signal traps & stale lock (`is_lock_stale`, `break_stale_lock`) auto-cleanup. - - Group C: `adoption_metrics.py` (41KB) refactored into `scripts/adoption_metrics/` subpackage. - - Group D: `skills/adr-toolkit/scripts/commands/doctor.py` (`adr doctor` diagnostic command). - - Group E: 10MB file size cap & streaming parse protection in `frontmatter.py`. - - Group F: `--verbose`, `--debug`, `--quiet` logging flags & `doctor` subcommand integrated in `adr.py`. +- Governance config: `.github/CODEOWNERS`, `.github/dependabot.yml`, + `.github/labeler.yml`, `.github/labels.yml`, `.github/ISSUE_TEMPLATE/**`, + `.github/workflows/{labeler,labels,test,release}.yml`. +- Tooling/tests: `pyproject.toml`, `scripts/export_dev_requirements.py`, + `tests/unit/test_github_governance.py`, pre-existing ruff cleanup files. +- Docs: `SECURITY.md`, `project-roadmap.md`, `docs/enterprise-adoption.md`, + `docs/oss-repository-governance-audit.md`, governance design spec, + `changelog.md`, `improvements.md`. -## Next step (for a new session picking this up cold) +## Live GitHub changes applied -All P1/P2 Production Readiness backlog items are resolved and committed, and -the Cline CLI adapter (`adapters/cline/`, GitHub issue #19) is merged. Remaining -future work is Low priority (CODEOWNERS once there are 2+ qualified -maintainers, organization-wide governance once there are 2+ repositories). -Concretely: +- Synced labels without deleting unrelated labels; added `type:epic` this session. +- Enabled Discussions, Dependabot security updates/alerts, secret scanning and + push protection, private vulnerability reporting, and delete-branch-on-merge. +- Re-verified active branch ruleset `22101891` and tag ruleset `22102322`. -1. `improvements.md`'s `### Low` → audit-report sub-group has exactly 1 - item left (Antigravity in `harness-parity`), blocked on `agy` having - no public package registry — don't start it without re-verifying that - fact changed. Its enterprise-adoption.md sub-group has 3 - precondition-gated items (repository going public, 2+ maintainers, - 2+ repositories) — **not pure code tasks**. -2. A GitHub Wiki was considered and explicitly declined for now — this - project's docs-as-ADRs model (versioned, reviewed, tied to releases) - already covers the need; a wiki would fragment that. Revisit only - once the repo is public and community-contributed FAQ/tutorial - content that doesn't fit README/examples actually starts - accumulating. -3. If the user says "continue" without naming a task: say there is no - ready-to-start backlog item and ask what's next rather than - inventing scope. -4. If the user references a new audit finding or a fresh problem: use - the pattern this project uses for hardening work — writing-plans -> - executing-plans, TDD, one commit per task, verify real test/mypy - output before each commit — rather than skipping straight to edits. -5. This repository enforces a local `.githooks/pre-push` hook that - blocks direct pushes to `develop`/`master` (no GitHub branch - protection is configured — the repo is private, which is a GitHub - Pro-only feature — so the hook is the *only* enforcement). Any merge - into either branch needs a short-lived branch + `gh pr create` + - `gh pr merge`, not a direct push. A release follows Git Flow: tag - from `master` only, after a `release/*` (or `hotfix/*` for a - post-release bug) branch merges in via PR, then merge `master` back - into `develop`. -6. GitHub Artifact Attestation (`.github/workflows/release.yml`) is - skipped while this repository is private (GitHub rejects it for a - user-owned private repo) and starts running automatically once the - repo goes public — no workflow change needed then. +## Next step + +1. Merge PR #34 (`feat(github): add OSS repository governance & supply-chain + hardening`) into `develop` after its CI checks pass. +2. After merge, update ruleset `22101891`: remove the two Python 3.9 contexts; + add all three Python 3.10 contexts plus `lint` and `dependency-audit`; query + effective rules again. ## Verification -`python3 -m pytest tests/unit tests/integration -q` (550 tests passing) and -`python3 scripts/sync_version.py --check` passed cleanly. +- CI-equivalent pytest + branch coverage: 558 passed, 92.74% coverage. +- `ruff check .`: passed. +- scoped `mypy --strict`: passed. +- `sync_version.py --check` and `verify_examples.py --check`: passed. +- `pip-audit --strict` in a clean dev-tool environment: no known vulnerabilities. +- actionlint v1.7.12: passed after fixing PR-title expression injection. +- Python package build: wheel and sdist built successfully; emitted only the + tracked PyPA license-metadata deprecation warning. +- Antigravity 1.1.27 artifact: official SHA-512 matched and archive contained + the expected `antigravity` binary. ## Open risks -- The ReDoS runtime timeout (`rules/conflict.py`) is POSIX-only. -- `supersede.py` guarantees single-file atomicity, but true two-phase multi-file commit across pair updates is scoped out. +- Until the governance PR is merged and ruleset contexts are updated, the live + ruleset still names removed Python 3.9 checks and does not require the new + `lint`/`dependency-audit` jobs. +- `pypa/gh-action-pypi-publish` remains `continue-on-error: true`, so a release + can partially succeed; tracked in `improvements.md`. +- Project/milestone/stale automation is deliberately deferred until issue/PR + volume meets the triggers in the audit report. diff --git a/improvements.md b/improvements.md index ab7960e..3c96623 100644 --- a/improvements.md +++ b/improvements.md @@ -52,6 +52,10 @@ Backlog derived from `docs/adr-toolkit-audit-report.md`, operational experiences - [ ] *(전제조건: 저장소 public 전환)* **Public 전환 게이트 실제 적용** — PR template/`CONTRIBUTING.md`/`SECURITY.md`는 이미 존재함. 남은 건 `master`/`develop`/`v*` 태그에 대한 실제 GitHub ruleset(PR 필수, required CI, conversation resolution, force-push/삭제 차단) 적용과 API로 실제 상태 재조회뿐 — 코드 작업이 아니라 저장소를 public 전환한 뒤 GitHub 설정/API에서 해야 하는 작업. `project_v1_public_release_plan` 메모리 참고(1.0.0 시점 public 전환 계획). (enterprise-adoption.md §4, §9) - [ ] *(전제조건: qualified maintainer 2명 이상)* **CODEOWNERS 독립 승인 활성화** — 현재 1인 운영 상태에서 필수 code-owner review를 켜면 운영을 막거나 형식적 self-review만 만든다고 보고서 자체가 명시적으로 경고함. 인원 조건 충족 전엔 시작하지 않음. (enterprise-adoption.md §4, §9 "지금 구현하지 않을 것") - [ ] *(전제조건: 저장소 2개 이상)* **조직 단위 ruleset/reusable workflow/audit export/taxonomy** — 여러 저장소가 같은 운영 문제를 반복할 때 설계 시작. 지금은 저장소가 1개뿐이라 시작 조건 미충족. (enterprise-adoption.md §6, §8 항목 5) +- [ ] *(다음 governance PR merge 직후)* **required-check context 동기화** — ruleset `22101891`에서 Python 3.9 context를 제거하고 Python 3.10 matrix, `lint`, `dependency-audit`를 required로 추가한 뒤 effective-rules API로 재검증한다. +- [ ] *(동일 drift 재발 시)* **ruleset 설정 검증 자동화** — classic branch-protection API와 repository ruleset API를 혼동한 감사 오류 및 CI check-name drift가 다시 발생하면 ruleset-as-code 또는 read-only verification script를 도입한다. +- [ ] **PyPI publish fail-closed 재검토** — Trusted Publisher가 안정화되면 release workflow의 `continue-on-error: true`를 제거해 GitHub Release와 PyPI가 부분 성공으로 갈라지지 않게 한다. +- [ ] **PyPA license metadata 현대화** — 2027-02-18 이전에 deprecated `project.license` table과 license classifier를 SPDX expression / `license-files`로 전환하고 최소 setuptools 버전을 맞춘다. ## Done diff --git a/project-roadmap.md b/project-roadmap.md index 60f5827..2b02bbf 100644 --- a/project-roadmap.md +++ b/project-roadmap.md @@ -19,10 +19,10 @@ before implementation. Concrete selected work belongs in `improvements.md`. - Extend `harness-parity` coverage beyond `preflight`/`init`/`validate` to `check`, `search`, `graph`, and `create` once a real regression in one of those commands under a specific harness demonstrates the gap matters. -- Automate the Antigravity CLI (`agy`) adapter the same way once it has a - package-registry distribution a CI runner can install non-interactively; - today it has none, so `adapters/antigravity/README.md`'s manual - verification is the only signal. +- **Automate the Antigravity CLI (`agy`) adapter install-and-run verification** + — **Done (2026-09-05).** `harness-parity` downloads a versioned Linux release + artifact, verifies its official SHA-512 digest, and exercises the adapter. + The mutable `curl | bash` bootstrapper is intentionally not executed in CI. - **Harness-specific hook support beyond Claude Code SessionStart when equivalent stable extension points exist** — **Evaluated, not pursued (2026-08-31).** The precondition is now true: Codex CLI has a config-driven `SessionStart`/`UserPromptSubmit` hook system (`~/.codex/hooks.json`), and Gemini CLI ships `gemini hooks migrate` specifically to port Claude Code hooks over. But ADR Toolkit doesn't use a hook even on Claude Code today (it relies entirely on skill auto-discovery), and a hook that fires on every session regardless of relevance cuts against this project's own restraint principle (max 3 questions, judge what's significant, minimize interruption). The plausible use cases (nudge about an unfinished draft ADR, warn about a governed path) are already covered by deliberately invoking `discover` and `check` rather than an always-on hook. Revisit only if real usage shows people miss something that `discover`/`check` can't catch without a session-start nudge -- not just because the extension points now exist. ## ADR navigation and scale @@ -43,9 +43,10 @@ before implementation. Concrete selected work belongs in `improvements.md`. ## Public and enterprise governance -- After the repository becomes public, apply and API-verify branch/tag - protections, required checks, conversation resolution, force-push/deletion - controls, and a documented bypass policy. +- **Apply and API-verify repository branch/tag rulesets** — **Done + (2026-09-02; re-verified 2026-09-06).** Active rulesets protect + `master`/`develop`/`release/*` and `v*`; required-check names must be updated + whenever the CI matrix changes. - Add CODEOWNERS and mandatory independent review when the contributor model can actually satisfy it. - Organization-level rulesets, reusable workflows, RBAC, audit export, diff --git a/pyproject.toml b/pyproject.toml index 58f8e4b..f284080 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,19 +14,36 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Documentation", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [] [project.scripts] adr = "scripts.adr:main" +[project.optional-dependencies] +dev = [ + "pytest==9.1.1", + "pytest-cov==7.1.0", + "mypy==1.19.1", + "ruff==0.16.6", + "PyYAML==6.0.3", + "tomli==2.4.0; python_version < '3.11'", + "build==1.4.4", +] + +[tool.ruff] +target-version = "py310" +extend-exclude = ["docs/decisions"] + +[tool.ruff.lint] +select = ["E9", "F"] + [tool.setuptools] packages = ["scripts", "scripts.core", "scripts.commands", "scripts.rules"] package-dir = {"scripts" = "skills/adr-toolkit/scripts"} diff --git a/scripts/adoption_metrics/reporting.py b/scripts/adoption_metrics/reporting.py index d58bd23..9bcf4ad 100644 --- a/scripts/adoption_metrics/reporting.py +++ b/scripts/adoption_metrics/reporting.py @@ -2,8 +2,6 @@ import argparse import json -import subprocess -import sys from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Optional diff --git a/scripts/export_dev_requirements.py b/scripts/export_dev_requirements.py new file mode 100644 index 0000000..c329db1 --- /dev/null +++ b/scripts/export_dev_requirements.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Export the pinned ``dev`` extra from pyproject.toml as requirements.""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from typing import Sequence + +try: + import tomllib +except ModuleNotFoundError: # Python 3.10 + import tomli as tomllib # type: ignore[no-redef] + + +def dev_requirements(pyproject: Path) -> list[str]: + with pyproject.open("rb") as stream: + document = tomllib.load(stream) + return list(document["project"]["optional-dependencies"]["dev"]) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--pyproject", type=Path, default=Path("pyproject.toml")) + args = parser.parse_args(argv) + print(*dev_requirements(args.pyproject), sep="\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/verify_examples.py b/scripts/verify_examples.py index acc33e1..d2b5b9a 100644 --- a/scripts/verify_examples.py +++ b/scripts/verify_examples.py @@ -13,8 +13,6 @@ import argparse import json -import os -import re import subprocess import sys import tempfile @@ -250,7 +248,7 @@ def main(argv=None) -> int: parser = argparse.ArgumentParser(description="Verify and sync examples/*.md with adr.py") parser.add_argument("--check", action="store_true", help="Check that examples are executable and valid") parser.add_argument("--update", action="store_true", help="Auto-update examples if needed") - args = parser.parse_args(argv) + parser.parse_args(argv) if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace") diff --git a/skills/adr-toolkit/scripts/core/atomic_io.py b/skills/adr-toolkit/scripts/core/atomic_io.py index 8b38115..0bdc9aa 100644 --- a/skills/adr-toolkit/scripts/core/atomic_io.py +++ b/skills/adr-toolkit/scripts/core/atomic_io.py @@ -17,7 +17,6 @@ from contextlib import contextmanager from pathlib import Path from typing import Any, Iterator -from types import FrameType if sys.platform == "win32": import msvcrt diff --git a/tests/integration/test_index_markdown_injection.py b/tests/integration/test_index_markdown_injection.py index f182bc8..39bc19c 100644 --- a/tests/integration/test_index_markdown_injection.py +++ b/tests/integration/test_index_markdown_injection.py @@ -1,7 +1,6 @@ """Proves INDEX's generated README cannot be split into a second, attacker-controlled link via an ADR title (docs/adr-toolkit-audit-report.md, Top-3 #2).""" -from pathlib import Path from types import SimpleNamespace from scripts.commands import index diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index d986376..3d7faa1 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -1,5 +1,4 @@ import json -import os import pytest diff --git a/tests/unit/test_dependency_scanner.py b/tests/unit/test_dependency_scanner.py index 1ef8a8f..07f5a94 100644 --- a/tests/unit/test_dependency_scanner.py +++ b/tests/unit/test_dependency_scanner.py @@ -1,4 +1,3 @@ -from pathlib import Path from scripts.evidence import dependency_scanner diff --git a/tests/unit/test_github_governance.py b/tests/unit/test_github_governance.py new file mode 100644 index 0000000..75092df --- /dev/null +++ b/tests/unit/test_github_governance.py @@ -0,0 +1,102 @@ +from pathlib import Path +import subprocess +import sys + +import yaml + + +ROOT = Path(__file__).resolve().parents[2] + + +def load_yaml(relative_path: str) -> object: + with (ROOT / relative_path).open(encoding="utf-8") as stream: + return yaml.safe_load(stream) + + +def test_dependabot_updates_merge_through_develop() -> None: + config = load_yaml(".github/dependabot.yml") + + assert isinstance(config, dict) + updates = config["updates"] + assert all(update["target-branch"] == "develop" for update in updates) + + +def test_cli_paths_receive_the_cli_area_label() -> None: + config = load_yaml(".github/labeler.yml") + + assert isinstance(config, dict) + cli_rules = config["area:cli"] + patterns = cli_rules[0]["changed-files"][0]["any-glob-to-any-file"] + assert "skills/adr-toolkit/scripts/adr.py" in patterns + assert "skills/adr-toolkit/scripts/commands/**" in patterns + + +def test_ci_does_not_pipe_remote_installers_to_a_shell() -> None: + workflow = (ROOT / ".github/workflows/test.yml").read_text(encoding="utf-8") + + assert "install.sh | bash" not in workflow + + +def test_dependency_audit_installs_dev_requirements_from_pyproject() -> None: + workflow = (ROOT / ".github/workflows/test.yml").read_text(encoding="utf-8") + + assert "scripts/export_dev_requirements.py" in workflow + + +def test_dev_requirement_export_reads_the_requested_extra(tmp_path: Path) -> None: + pyproject = tmp_path / "pyproject.toml" + pyproject.write_text( + '[project.optional-dependencies]\n' + 'dev = ["pytest==9.1.1", "ruff==0.16.6"]\n' + 'docs = ["mkdocs==1.6.1"]\n', + encoding="utf-8", + ) + + result = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts/export_dev_requirements.py"), + "--pyproject", + str(pyproject), + ], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr + assert result.stdout == "pytest==9.1.1\nruff==0.16.6\n" + + +def test_release_uses_pinned_dependencies_and_action_commits() -> None: + workflow = (ROOT / ".github/workflows/release.yml").read_text(encoding="utf-8") + + assert 'pip install -e ".[dev]"' in workflow + assert "pip install pytest build" not in workflow + for line in workflow.splitlines(): + if "uses:" in line: + ref = line.split("uses:", 1)[1].strip().split("#", 1)[0].strip() + revision = ref.rsplit("@", 1)[1] + assert len(revision) == 40 + assert all(character in "0123456789abcdef" for character in revision) + + +def test_pr_title_is_not_interpolated_into_a_shell_script() -> None: + workflow = (ROOT / ".github/workflows/test.yml").read_text(encoding="utf-8") + + assert 'PR_TITLE: ${{ github.event.pull_request.title }}' in workflow + assert 'TITLE="${{ github.event.pull_request.title }}"' not in workflow + + +def test_labeler_write_permissions_are_scoped_per_job() -> None: + workflow = load_yaml(".github/workflows/labeler.yml") + + assert isinstance(workflow, dict) + assert workflow["permissions"] == {"contents": "read"} + assert workflow["jobs"]["label-pr"]["permissions"] == { + "contents": "read", + "pull-requests": "write", + } + assert workflow["jobs"]["triage-issue"]["permissions"] == { + "issues": "write" + } diff --git a/tests/unit/test_identifiers.py b/tests/unit/test_identifiers.py index f105801..f09a757 100644 --- a/tests/unit/test_identifiers.py +++ b/tests/unit/test_identifiers.py @@ -1,4 +1,3 @@ -from pathlib import Path import pytest diff --git a/tests/unit/test_repository_paths.py b/tests/unit/test_repository_paths.py index c0f2997..512a513 100644 --- a/tests/unit/test_repository_paths.py +++ b/tests/unit/test_repository_paths.py @@ -1,6 +1,5 @@ """Tests for resolve_from_root's boundary enforcement (docs/adr-toolkit-audit-report.md §2.2 2.3).""" -from pathlib import Path import pytest