diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3c327fa..41e0b4a 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,5 +1,5 @@ { "name": "adr-toolkit", - "version": "1.0.1", + "version": "1.1.0", "description": "Initialize, record, and check Architecture Decision Records by inspecting the repository and existing decisions before asking questions." } 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/.gitignore b/.gitignore index e01e0c9..edc00a8 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ env/ adapters/codex/skills/adr-toolkit adapters/gemini-cli/skills/adr-toolkit adapters/antigravity/skills/adr-toolkit +.cline/skills/adr-toolkit # Task-by-task implementation plans (superpowers subagent-driven-development # artifacts) — kept locally for reference, not part of the public tree. diff --git a/AGENTS.md b/AGENTS.md index 47398d5..db0e568 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,10 @@ # AGENTS.md This is the shared operating document for every agent harness working in this -repository (Codex, Claude Code, Gemini, or any other). Harness-specific entry -files (`CODEX.md`, `CLAUDE.md`, `GEMINI.md`) are thin pointers back to this -file — read this one first, regardless of which harness started the session. +repository (Codex, Claude Code, Gemini, Cline, or any other). Harness-specific +entry files (`CODEX.md`, `CLAUDE.md`, `GEMINI.md`, `CLINE.md`) are thin pointers +back to this file — read this one first, regardless of which harness started +the session. ## Source of truth @@ -65,9 +66,13 @@ without reading commit-by-commit history. ## Harness entry files -`CODEX.md`, `CLAUDE.md`, and `GEMINI.md` stay thin. They point back to this -file and hold only notes specific to that harness (e.g. a tool quirk). Do not -duplicate rules from this file into them. +`CODEX.md`, `CLAUDE.md`, `GEMINI.md`, and `CLINE.md` stay thin. They point +back to this file and hold only notes specific to that harness (e.g. a tool +quirk). Do not duplicate rules from this file into them. Per-model entry +files (e.g. `DEEPSEEK.md`, `GLM.md`, `KIMI.md`, `QWEN.md`) are not created: +those are model/API providers reached through a harness (Cline, Cursor, +Aider, …), not harnesses themselves, so the harness's entry file already +covers them. ## Safety diff --git a/CLINE.md b/CLINE.md new file mode 100644 index 0000000..b4b6a2b --- /dev/null +++ b/CLINE.md @@ -0,0 +1,23 @@ +# CLINE.md + +Follow the shared project rules in `AGENTS.md` first. + +Use this file only for Cline-specific notes that cannot live in the shared +document. + +## Cline-specific notes + +Cline discovers this project as **Project Skill** via the open Agent Skills +standard (`skills/adr-toolkit/SKILL.md`); see `adapters/cline/README.md` for +install details. Unlike Codex/Gemini/Antigravity, Cline has no adapter-local +manifest — the canonical `SKILL.md` frontmatter (`name: adr-toolkit`, +`description` < 1024 chars) already satisfies Cline's skill-discovery +requirements. + +Cline (confirmed on CLI 3.0.61 with the `cline-pass/glm-5.2` model) auto-injects +the repository-root `AGENTS.md` into the workspace context at session start, so +the shared operating document reaches every model routed through ClinePass +(DeepSeek, GLM, Kimi, Qwen, …) without a per-model entry file. Per-model +`DEEPSEEK.md` / `KIMI.md` / `GLM.md` / `QWEN.md` files are deliberately not +created — those are model providers, not harnesses; the harness (Cline) is the +layer that owns project-context files. diff --git a/README.md b/README.md index f70893a..f2fa9a7 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ of these adapters at it: | [Codex CLI](adapters/codex/) | Agent Plugins 1.0.0 manifest | [`adapters/codex/README.md`](adapters/codex/README.md) | | [Gemini CLI](adapters/gemini-cli/) | Extension manifest | [`adapters/gemini-cli/README.md`](adapters/gemini-cli/README.md) | | [Antigravity CLI](adapters/antigravity/) | Plugin manifest | [`adapters/antigravity/README.md`](adapters/antigravity/README.md) | +| [Cline CLI](adapters/cline/) | Open Agent Skills (no manifest) | [`adapters/cline/README.md`](adapters/cline/README.md) | | Anything else | Generic fallback | [`adapters/generic/README.md`](adapters/generic/README.md) — needs only markdown-reading and shell | **No AI harness at all?** `create --interactive` runs the same interview 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/adapters/antigravity/plugin.json b/adapters/antigravity/plugin.json index 118e48c..03ae7ba 100644 --- a/adapters/antigravity/plugin.json +++ b/adapters/antigravity/plugin.json @@ -1,6 +1,6 @@ { "$schema": "https://antigravity.google/schemas/v1/plugin.json", "name": "adr-toolkit", - "version": "1.0.1", + "version": "1.1.0", "description": "Initialize, record, and check Architecture Decision Records by inspecting the repository and existing decisions before asking questions." } diff --git a/adapters/cline/README.md b/adapters/cline/README.md new file mode 100644 index 0000000..5c6cb34 --- /dev/null +++ b/adapters/cline/README.md @@ -0,0 +1,116 @@ +# Cline CLI adapter + +Cline CLI installs skills through the **open Agent Skills** standard — the same +`SKILL.md` + `skills/` convention this toolkit already ships — rather than a +harness-specific manifest file like the Codex, Gemini, and Antigravity adapters +use. There is no `plugin.json`/`gemini-extension.json` equivalent for Cline +skills, so this adapter is **README-only** (like `adapters/generic/`): it +documents how to point Cline at the existing `skills/adr-toolkit/` package. +**Manually verified against Cline CLI 3.0.61** (`cline --version`): discovery, +install, and the installed script layer all work — see "Verification status" +below. + +Cline CLI exposes a `skill` subcommand that forwards to the open skills CLI +(`npx skills`), and it discovers skills from the standard agent locations +(`~/.agents/skills/` globally, `.cline/skills/` per project). Because +`skills/adr-toolkit/SKILL.md` already carries the two fields Cline requires — +`name` matching its directory and `description` (under 1024 characters) — the +package installs as-is with no wrapper. + +## Install + +### From the published repository (recommended) + +The repository is public, so Cline installs the skill straight from GitHub: + +```bash +cline skill add SHcommit/ADR-toolkit --global +``` + +Add `-y`/`--yes` to skip the confirmation prompt. Drop `--global` to install +project-level (into the current project's `.cline/skills/`) instead of globally. + +### From a local clone + +A local checkout works the same way: + +```bash +cline skill add "$(pwd)/skills/adr-toolkit" --global +``` + +or symlink the package into Cline's skill directory yourself: + +```bash +# project-level +mkdir -p .cline/skills +ln -s "$(pwd)/skills/adr-toolkit" .cline/skills/adr-toolkit + +# global +mkdir -p ~/.agents/skills +ln -s "$(pwd)/skills/adr-toolkit" ~/.agents/skills/adr-toolkit +``` + +The symlink is created at install time, not committed to any repo — committing a +real symlink breaks on Windows checkouts that don't have `core.symlinks` enabled, +which this project's CI can't assume. + +### Confirm + +```bash +cline skill list +``` + +should list `adr-toolkit`. + +### Script-layer fallback + +Either way, run +`python skills/adr-toolkit/scripts/adr.py preflight --json` from your target +repository to confirm the script layer works standalone — this is the verified +working path regardless of skill-discovery status. + +## Why there's no manifest here + +Cline's "plugin" mechanism (`cline plugin install`, driven by a `package.json` +`cline` field) is for **TypeScript `AgentPlugin` modules** that register tools +and lifecycle hooks — a different extension point from a skill. ADR Toolkit is a +Python skill, so a TS plugin wrapper would be a rewrite with no benefit. The +always-on-hook use case (auto-running `check` on session start) is deliberately +out of scope — see `project-roadmap.md`'s "Harness parity" note. + +## Verification status + +Manually verified against Cline CLI 3.0.61 (`cline --version`) in an isolated +`HOME=$(mktemp -d)` so no state was written to the real `~/.cline`/`~/.agents`. + +``` +$ cline skill add SHcommit/ADR-toolkit --list +... +◇ Found 1 skill +│ adr-toolkit +│ Initialize, record, and check Architecture Decision Records by inspecting the repository and existing decisions before asking questions. + +$ cline skill add SHcommit/ADR-toolkit --yes --global +... +◇ Installed 1 skill +│ ✓ adr-toolkit (copied) +│ → ~/.agents/skills/adr-toolkit +``` + +The installed snapshot lands under `~/.agents/skills/adr-toolkit/` and carries +the whole `skills/adr-toolkit/` package. The script layer was run directly out of +it against a scratch git repository, producing correct `"ok": true` output for +`preflight`, `init --dir docs/decisions`, and `validate --dir docs/decisions` — +matching the Codex, Gemini CLI, and Antigravity adapters' verified depth. + +Two details worth knowing: + +- **The install location is `~/.agents/skills/`, not `~/.cline/...`.** The + skills CLI reports `~/.agents/skills/adr-toolkit` as the install path (the open + Agent Skills global directory Cline reads), even though Cline's CLI reference + mentions a `~/.cline/data/settings/skills/` global directory. Trust + `cline skill list` for where a given Cline version actually looks. +- **A project's own `skills/` directory is auto-detected.** Running + `cline skill list` inside this repository reports `adr-toolkit` from + `./skills/adr-toolkit` as a project skill, so a checkout of this repo is already + a working Cline project skill with no install step. diff --git a/adapters/gemini-cli/gemini-extension.json b/adapters/gemini-cli/gemini-extension.json index 3c327fa..41e0b4a 100644 --- a/adapters/gemini-cli/gemini-extension.json +++ b/adapters/gemini-cli/gemini-extension.json @@ -1,5 +1,5 @@ { "name": "adr-toolkit", - "version": "1.0.1", + "version": "1.1.0", "description": "Initialize, record, and check Architecture Decision Records by inspecting the repository and existing decisions before asking questions." } diff --git a/changelog.md b/changelog.md index 15f4133..f2dd488 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,77 @@ 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 + manifest — documenting `cline skill add SHcommit/ADR-toolkit` plus a manual + symlink fallback. Manually verified against Cline CLI 3.0.61. +- Expanded `improvements.md` backlog with High and Medium priority items: ReDoS cross-platform safety, 2-phase atomic transaction rollback for `supersede`, ADR overlap/similarity Eval framework, weekly automated maintenance workflows, PR significance bot, `adr lint --fix` auto-repair, interactive HTML graph viewer, and code drift detection. +- Logged a Medium-priority backlog item to fold the Cline CLI adapter into the `harness-parity` CI job (currently manually verified against Cline CLI 3.0.61 only), so Cline/ClinePass version drift is caught automatically like the Codex, Gemini, and Antigravity adapters. +- Added `CLINE.md` as a thin harness entry pointer (matching `CODEX.md`/`CLAUDE.md`/`GEMINI.md`) and updated `AGENTS.md` to list Cline alongside the other harnesses, plus an explicit note that per-model files (`DEEPSEEK.md`/`GLM.md`/`KIMI.md`/`QWEN.md`) are intentionally not created — those are model providers routed through a harness, not harnesses themselves. Verified that Cline (CLI 3.0.61 with `cline-pass/glm-5.2`) auto-injects the repo-root `AGENTS.md` into workspace context at session start, so the shared operating document reaches every model routed through ClinePass without a per-model entry file. + +## v1.1.0 (2026-09-06) + +Minor release: new features, governance hardening, and a new harness adapter. + +- 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 + manifest — documenting `cline skill add SHcommit/ADR-toolkit` plus a manual + symlink fallback. Manually verified against Cline CLI 3.0.61. +- Added `CLINE.md` as a thin harness entry pointer (matching + `CODEX.md`/`CLAUDE.md`/`GEMINI.md`) and updated `AGENTS.md` to list Cline + alongside the other harnesses, plus an explicit note that per-model files + (`DEEPSEEK.md`/`GLM.md`/`KIMI.md`/`QWEN.md`) are intentionally not created — + those are model providers routed through a harness, not harnesses themselves. + Verified that Cline (CLI 3.0.61 with `cline-pass/glm-5.2`) auto-injects the + repo-root `AGENTS.md` into workspace context at session start, so the shared + operating document reaches every model routed through ClinePass without a + per-model entry file. +- 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. +- 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: 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. +- Recorded ADR-0017 (OSS repository governance & supply-chain hardening + decision). +- Logged a Medium-priority backlog item to fold the Cline CLI adapter into the + `harness-parity` CI job (currently manually verified against Cline CLI 3.0.61 + only), so Cline/ClinePass version drift is caught automatically like the Codex, + Gemini, and Antigravity adapters. +- Expanded `improvements.md` backlog with High and Medium priority items: ReDoS + cross-platform safety, 2-phase atomic transaction rollback for `supersede`, + ADR overlap/similarity Eval framework, weekly automated maintenance workflows, + PR significance bot, `adr lint --fix` auto-repair, interactive HTML graph + viewer, and code drift detection. +- Synced `skills/adr-toolkit/VERSION`, `SKILL.md` frontmatter, and the Claude / + Gemini / Antigravity / Codex manifests to 1.1.0 via `scripts/sync_version.py`. + ## v1.0.1 (2026-09-02) - Added PyPI packaging support (`pyproject.toml`) for `pip install adr-toolkit` and `pipx install adr-toolkit`. diff --git a/docs/decisions/0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md b/docs/decisions/0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md new file mode 100644 index 0000000..77791e8 --- /dev/null +++ b/docs/decisions/0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md @@ -0,0 +1,114 @@ +--- +id: ADR-0017 +title: Adopt source-controlled GitHub governance and CI supply-chain hardening +status: accepted +date: 2026-09-06 +locale: en +decision_makers: + - YangSeungHyun +related: + - ADR-0005 + - ADR-0016 +affected_paths: + - .github/CODEOWNERS + - .github/dependabot.yml + - .github/labeler.yml + - .github/labels.yml + - .github/ISSUE_TEMPLATE/ + - .github/workflows/test.yml + - .github/workflows/release.yml + - pyproject.toml + - scripts/export_dev_requirements.py + - tests/unit/test_github_governance.py + - SECURITY.md +tags: + - github + - governance + - supply-chain + - ci + - security +retrospective: false +--- + +# Adopt source-controlled GitHub governance and CI supply-chain hardening + +## Context and Problem Statement + +The repository became public on 2026-08-29 and shipped v1.0.0/v1.0.1, but the +day-to-day contributor mechanics that keep a growing issue/PR queue navigable +for a single maintainer were never built out. Labels were the GitHub defaults, +issue templates were legacy Markdown, and there was no Dependabot, no +CODEOWNERS, no path-based auto-labeler, no lint job, and no dependency/security +scan. A prior audit also queried only the classic branch-protection endpoint +and wrongly concluded the repo was unprotected; the repository ruleset API +showed active branch/tag rulesets since 2026-09-02, but their required +status-check names can drift whenever the CI matrix changes. + +## Decision Drivers + +* One maintainer and one repository: automation must pay for itself without + assuming a team that does not exist yet. +* CI was already mature (multi-OS pytest matrix, coverage floor, scoped mypy + --strict, drift gates, pr-title-check, harness-parity) but had no lint and no + dependency/security scan. +* harness-parity ran a remote `curl | bash` installer -- a supply-chain risk. +* Every "not now" item must carry a written trigger, not a vague "later". + +## Considered Options + +* Keep hand-triage only -- does not scale past a handful of issues. +* Adopt GitHub-native automation as code (labels, labeler, Dependabot, Issue + Forms) plus lint + dependency-audit CI gates, and sync the ruleset's required + checks to the CI matrix -- chosen. +* Add heavy automation now (mandatory CODEOWNERS review, stale bot, org-wide + rulesets) -- deferred behind explicit preconditions. + +## Decision Outcome + +Chosen option: **source-controlled GitHub governance + CI supply-chain +hardening**, because it lets issues/PRs self-organize and keeps required checks +honest without over-automating a one-maintainer repository. + +* Path-based auto-labeler mapping changed-file globs to `area:*` labels, plus a + source-controlled label taxonomy (`.github/labels.yml`) synced live without + deleting unrelated labels. +* Dependabot for pip and github-actions only, weekly, grouped, targeting + `develop`. +* Structured Issue Forms for bug and feature reports, with `config.yml` routing + questions to Discussions and vulnerabilities to `SECURITY.md`. +* Fast CI gates: ruff lint and `pip-audit --strict`; the Python floor moved from + 3.9 to 3.10. +* Supply-chain hardening: the Antigravity `curl | bash` installer was replaced + with a versioned SHA-512-verified artifact; release Actions were pinned to + commit SHAs; the PR-title shell expression injection was removed. +* Branch ruleset `22101891` required checks were synced to the new matrix + (Python 3.10 x3 + `lint` + `dependency-audit` added; Python 3.9 removed) and + re-verified through the effective-rules API. +* A dormant CODEOWNERS was drafted but not wired to required review. Live + Discussions, Dependabot security updates/alerts, secret scanning + push + protection, and private vulnerability reporting were enabled. + +## Consequences + +* Good: new issues/PRs arrive pre-labeled; dependency bumps arrive as routine, + reviewable PRs; CI fails fast on lint and known vulnerabilities; protected + branches can no longer be silently blocked by a stale required-check name. +* Bad: ruleset required checks are GitHub-side settings that cannot live fully + in code, so a future CI job rename still requires a manual ruleset sync; the + trigger for a read-only verification script is recorded in `improvements.md`. + +## Confirmation + +pytest (562 passed) plus branch coverage, ruff check, scoped mypy --strict, +`sync_version.py --check`, and `verify_examples.py --check` all passed. The +ruleset and effective-rules APIs confirm the new required checks are active for +`develop`. + +## Revisit Triggers + +* A second qualified maintainer appears -> enable CODEOWNERS required review. +* A second repository repeats the same operations -> org-level rulesets, + reusable workflows, audit export, and taxonomy. +* A real abandoned-issue backlog accumulates -> stale bot. +* CI check-name drift recurs -> ruleset-as-code or a read-only verification + script. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 02c424d..81c7f00 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -18,6 +18,7 @@ - [ADR-0014 — Typed result contracts and a scoped mypy --strict CI gate](0014-typed-result-contracts-and-a-scoped-mypy-strict-ci-gate.md) - [ADR-0015 — Structured JSON stderr logging with correlation IDs for uncaught errors](0015-structured-json-stderr-logging-with-correlation-ids-for-uncaught-errors.md) - [ADR-0016 — Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key](0016-sign-release-artifacts-with-github-artifact-attestation-instead-of-a-managed-signing-key.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### Superseded - [ADR-0003 — Localize only index.py's generated strings, not agent-composed text](0003-localize-only-index-py-s-generated-strings-not-agent-composed-text.md) @@ -38,6 +39,9 @@ - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0013 — Two-stage ReDoS defense for CHECK's author-supplied constraint regexes](0013-two-stage-redos-defense-for-check-s-author-supplied-constraint-regexes.md) +### ci +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + ### cli - [ADR-0009 — --json is a documented no-op; CLI output is always JSON](0009-json-flag-always-json-contract.md) @@ -75,9 +79,13 @@ ### git-flow - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) +### github +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + ### governance - [ADR-0008 — Deterministic CHECK policy exceptions: schema-validated, annotate-only, never suppress](0008-check-exceptions-annotate-only.md) - [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### graph - [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) @@ -115,9 +123,11 @@ ### security - [ADR-0013 — Two-stage ReDoS defense for CHECK's author-supplied constraint regexes](0013-two-stage-redos-defense-for-check-s-author-supplied-constraint-regexes.md) - [ADR-0016 — Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key](0016-sign-release-artifacts-with-github-artifact-attestation-instead-of-a-managed-signing-key.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### supply-chain - [ADR-0016 — Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key](0016-sign-release-artifacts-with-github-artifact-attestation-instead-of-a-managed-signing-key.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### typing - [ADR-0014 — Typed result contracts and a scoped mypy --strict CI gate](0014-typed-result-contracts-and-a-scoped-mypy-strict-ci-gate.md) @@ -148,17 +158,34 @@ ### `.claude-plugin/plugin.json` - [ADR-0004 — Adapter packaging: manifest-only directories, install-time symlinks, verified formats](0004-adapter-packaging-manifest-only-directories-install-time-symlinks-verified-formats.md) +### `.github/CODEOWNERS` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + +### `.github/ISSUE_TEMPLATE/` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + ### `.github/PULL_REQUEST_TEMPLATE.md` - [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) +### `.github/dependabot.yml` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + +### `.github/labeler.yml` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + +### `.github/labels.yml` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + ### `.github/workflows/` - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) ### `.github/workflows/release.yml` - [ADR-0016 — Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key](0016-sign-release-artifacts-with-github-artifact-attestation-instead-of-a-managed-signing-key.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### `.github/workflows/test.yml` - [ADR-0014 — Typed result contracts and a scoped mypy --strict CI gate](0014-typed-result-contracts-and-a-scoped-mypy-strict-ci-gate.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### `.gitignore` - [ADR-0004 — Adapter packaging: manifest-only directories, install-time symlinks, verified formats](0004-adapter-packaging-manifest-only-directories-install-time-symlinks-verified-formats.md) @@ -178,6 +205,7 @@ ### `SECURITY.md` - [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) - [ADR-0016 — Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key](0016-sign-release-artifacts-with-github-artifact-attestation-instead-of-a-managed-signing-key.md) +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) ### `adapters/` - [ADR-0004 — Adapter packaging: manifest-only directories, install-time symlinks, verified formats](0004-adapter-packaging-manifest-only-directories-install-time-symlinks-verified-formats.md) @@ -200,6 +228,12 @@ ### `project-roadmap.md` - [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) +### `pyproject.toml` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + +### `scripts/export_dev_requirements.py` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + ### `scripts/sync_version.py` - [ADR-0005 — Adopt Git Flow with direct-tag release automation](0005-adopt-git-flow-with-direct-tag-release-automation.md) @@ -334,6 +368,9 @@ ### `tests/unit/test_contracts.py` - [ADR-0014 — Typed result contracts and a scoped mypy --strict CI gate](0014-typed-result-contracts-and-a-scoped-mypy-strict-ci-gate.md) +### `tests/unit/test_github_governance.py` +- [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) + ### `tests/unit/test_graph_command.py` - [ADR-0011 — Expose ADR relationships as Mermaid and SVG navigation artifacts](0011-adr-relationship-graph-public-readiness.md) @@ -348,6 +385,7 @@ ## Chronological (newest first) +- 2026-09-06 — [ADR-0017 — Adopt source-controlled GitHub governance and CI supply-chain hardening](0017-adopt-source-controlled-github-governance-and-ci-supply-chain-hardening.md) - 2026-09-01 — [ADR-0012 — Atomic writes and cross-platform directory locking for CREATE, EXCEPTION, and SUPERSEDE](0012-atomic-writes-and-cross-platform-directory-locking-for-create-exception-and-supersede.md) - 2026-09-01 — [ADR-0013 — Two-stage ReDoS defense for CHECK's author-supplied constraint regexes](0013-two-stage-redos-defense-for-check-s-author-supplied-constraint-regexes.md) - 2026-09-01 — [ADR-0014 — Typed result contracts and a scoped mypy --strict CI gate](0014-typed-result-contracts-and-a-scoped-mypy-strict-ci-gate.md) @@ -386,6 +424,8 @@ - ADR-0015 "Structured JSON stderr logging with correlation IDs for uncaught errors" related to: ADR-0009 "--json is a documented no-op; CLI output is always JSON" - ADR-0016 "Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key" related to: ADR-0005 "Adopt Git Flow with direct-tag release automation" - ADR-0016 "Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key" related to: ADR-0011 "Expose ADR relationships as Mermaid and SVG navigation artifacts" +- ADR-0017 "Adopt source-controlled GitHub governance and CI supply-chain hardening" related to: ADR-0005 "Adopt Git Flow with direct-tag release automation" +- ADR-0017 "Adopt source-controlled GitHub governance and CI supply-chain hardening" related to: ADR-0016 "Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key" ```mermaid flowchart LR @@ -405,6 +445,7 @@ flowchart LR ADR_0014["ADR-0014
Typed result contracts and a scoped mypy --strict CI gate"] ADR_0015["ADR-0015
Structured JSON stderr logging with correlation IDs for uncaught errors"] ADR_0016["ADR-0016
Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key"] + ADR_0017["ADR-0017
Adopt source-controlled GitHub governance and CI supply-chain hardening"] ADR_0006 -.->|related| ADR_0003 ADR_0006 -->|supersedes| ADR_0003 ADR_0011 -.->|related| ADR_0001 @@ -419,4 +460,6 @@ flowchart LR ADR_0015 -.->|related| ADR_0009 ADR_0016 -.->|related| ADR_0005 ADR_0016 -.->|related| ADR_0011 + ADR_0017 -.->|related| ADR_0005 + ADR_0017 -.->|related| ADR_0016 ``` diff --git a/docs/decisions/relationships.mmd b/docs/decisions/relationships.mmd index 91fe037..f13530d 100644 --- a/docs/decisions/relationships.mmd +++ b/docs/decisions/relationships.mmd @@ -15,6 +15,7 @@ flowchart LR ADR_0014["ADR-0014
Typed result contracts and a scoped mypy --strict CI gate"] ADR_0015["ADR-0015
Structured JSON stderr logging with correlation IDs for uncaught errors"] ADR_0016["ADR-0016
Sign release artifacts with GitHub Artifact Attestation instead of a managed signing key"] + ADR_0017["ADR-0017
Adopt source-controlled GitHub governance and CI supply-chain hardening"] ADR_0006 -.->|related| ADR_0003 ADR_0006 -->|supersedes| ADR_0003 ADR_0011 -.->|related| ADR_0001 @@ -29,3 +30,5 @@ flowchart LR ADR_0015 -.->|related| ADR_0009 ADR_0016 -.->|related| ADR_0005 ADR_0016 -.->|related| ADR_0011 + ADR_0017 -.->|related| ADR_0005 + ADR_0017 -.->|related| ADR_0016 diff --git a/docs/decisions/relationships.svg b/docs/decisions/relationships.svg index b267074..7571a90 100644 --- a/docs/decisions/relationships.svg +++ b/docs/decisions/relationships.svg @@ -1,4 +1,4 @@ - + ADR relationship graph Vector export of ADR supersession and related-decision links. @@ -52,32 +52,39 @@ ADR-0016 Sign release artifacts with GitHu... + +ADR-0017 +Adopt source-controlled GitHub go... -ADR-0006 related ADR-0003 +ADR-0006 related ADR-0003 -ADR-0006 supersedes ADR-0003 +ADR-0006 supersedes ADR-0003 -ADR-0011 related ADR-0001 +ADR-0011 related ADR-0001 -ADR-0011 related ADR-0006 +ADR-0011 related ADR-0006 -ADR-0011 related ADR-0008 +ADR-0011 related ADR-0008 -ADR-0011 related ADR-0009 +ADR-0011 related ADR-0009 -ADR-0012 related ADR-0006 +ADR-0012 related ADR-0006 -ADR-0013 related ADR-0002 +ADR-0013 related ADR-0002 -ADR-0013 related ADR-0007 +ADR-0013 related ADR-0007 -ADR-0013 related ADR-0008 +ADR-0013 related ADR-0008 -ADR-0014 related ADR-0009 +ADR-0014 related ADR-0009 -ADR-0015 related ADR-0009 +ADR-0015 related ADR-0009 -ADR-0016 related ADR-0005 +ADR-0016 related ADR-0005 -ADR-0016 related ADR-0011 +ADR-0016 related ADR-0011 + +ADR-0017 related ADR-0005 + +ADR-0017 related ADR-0016 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 67780d2..8887e4f 100644 --- a/handoff.md +++ b/handoff.md @@ -2,32 +2,58 @@ ## Current task -Completed Production Readiness P1/P2 Backlog Improvements via Parallel Subagent Execution (Groups A-F). -Strictly audited codebase improvements completed; all 550 tests passing. - -## Scope - -- 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`. - -## Next step (for a new session picking this up cold) - -- All P1/P2 Production Readiness backlog items are resolved and committed. -- Future work: Low priority tasks (CODEOWNERS once there are 2+ qualified maintainers, organization-wide governance once there are 2+ repositories). +Release v1.1.0: VERSION bumped 1.0.1 → 1.1.0, manifests synced via +`scripts/sync_version.py`, `changelog.md` v1.1.0 section added. Release branch +`release/v1.1.0` off `develop` ready to merge into `master`; after merge, tag +`v1.1.0` will be pushed from `master` to trigger `.github/workflows/release.yml`. + +## Touched files + +- `skills/adr-toolkit/VERSION` — bumped to `1.1.0`. +- `skills/adr-toolkit/SKILL.md` — frontmatter `version: 1.1.0`. +- `.claude-plugin/plugin.json`, `adapters/gemini-cli/gemini-extension.json`, + `adapters/antigravity/plugin.json` — `version` synced to `1.1.0`. +- `changelog.md` — moved the Unreleased block under a new `## v1.1.0 (2026-09-06)` + heading summarizing the 31 commits accumulated since v1.0.1 (Cline adapter, + CLINE.md/AGENTS.md harness entry, GitHub governance hardening, CI supply-chain + hardening, ADR-0017, backlog items). +- `handoff.md` — this file. + +## Why 1.1.0 (MINOR, not PATCH) + +The 31 commits between v1.0.1 and this release include six `feat:` commits +(Cline CLI adapter, GitHub label taxonomy / labeler / dependabot / Issue Forms / +auto-triage). SemVer requires a MINOR bump for new backward-compatible +features; no breaking changes were identified, so MAJOR is not warranted and +PATCH would understate the surface change. + +## Next step + +1. Open PR `release/v1.1.0` → `master` (AGENTS.md: "Release branches merge + into `master` and back into `develop`"). +2. After CI passes (release.yml runs pytest + sync_version --check + tag == + VERSION), merge into `master`. +3. Back-merge `master` → `develop` (PR), per the git flow. +4. Tag `v1.1.0` from `master` and push — `release.yml` runs the full suite, + verifies manifest versions against the tag, and publishes a GitHub + Release (plus PyPI publish via Trusted Publisher, `continue-on-error`). +5. Delete the short-lived `release/v1.1.0` branch after merge. ## Verification -`python3 -m pytest tests/unit tests/integration -q` (550 tests passing) and -`python3 scripts/sync_version.py --check` passed cleanly. +- `scripts/sync_version.py --check`: passes (VERSION, SKILL.md, and all + 4 manifests agree on 1.1.0; no untracked manifests). +- `changelog.md` reflects all 31 v1.0.1..develop commits. +- Working-tree state on `release/v1.1.0`: clean except for the version-sync + + changelog/handoff commits. ## 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. +- `pypa/gh-action-pypi-publish` remains `continue-on-error: true`, so the + PyPI publish step can partially fail without failing the release job — + tracked in `improvements.md`. +- Cline adapter is still "Manually verified against 3.0.61" only; the + `harness-parity` CI job does not yet cover Cline — PR #36 logged this as a + Medium backlog item to implement after v1.1.0 ships. +- Inherits prior Open risks (ruleset context sync post-merge, deferred + project/milestone/stale automation). diff --git a/improvements.md b/improvements.md index 9739f74..a2111e6 100644 --- a/improvements.md +++ b/improvements.md @@ -5,23 +5,68 @@ Concrete implementation backlog. Unscheduled product bets belong in ## Open -Backlog derived from `docs/adr-toolkit-audit-report.md`. Scope excludes -domains 1 (core/plugin architecture) and 5 (governance/FSM) — already -scored 72/80 and mostly "no action needed" in the audit. README prose is -another worktree's. +Backlog derived from `docs/adr-toolkit-audit-report.md`, operational experiences, and developer friction analysis. + +### High + +- [ ] **ReDoS 크로스 플랫폼 Guard (Windows 비-POSIX 타임아웃)** — `rules/conflict.py`의 정규식 ReDoS 타임아웃 구동 방식이 `signal.SIGALRM` 기반이라 POSIX 전용 환경으로 제한됨. Static constraint checker (`core/constraints.py`) 외 교차 정규식 타임아웃을 Windows에서도 안전하게 처리할 수 있도록 스레드/프로세스 타임아웃 워커 도입. (`handoff.md` Open Risk 대응) +- [ ] **ADR `supersede` 2단계 원자적 트랜잭션 롤백 보장** — `supersede` 커맨드가 기존 ADR 상태 변경(Superseded)과 신규 ADR 생성/수정(Superseding)을 연쇄 실행할 때, 프로세스 중간 강제 종료 시 발생할 수 있는 '반쪽짜리 업데이트' 방지를 위한 트랜잭션 백업/롤백 안전장치 구축. + +### Medium + +- [ ] **harness-parity CI에 Cline CLI 편입** — `adapters/cline/README.md` 기준 + 여전히 "Manually verified against Cline CLI 3.0.61"뿐, push/PR마다 자동 + 검증이 없어 Cline CLI/ClinePass 버전업 시 회귀를 CI가 못 잡음. Codex/Gemini + 어댑터는 이미 harness-parity job에서 자동 검증 중(`project-roadmap.md` + "Harness parity" 참조). Cline CLI는 npm 글로벌 설치가 가능하므로 + Antigravity(공개 패키지 레지스트리 미지원)와 달리 전제조건은 이미 충족. + 선행 검증 항목: (1) `cline` npm 패키지명/배포 채널/안정 버전 핀 값 확인, + (2) CI runner에서 `cline skill add SHcommit/ADR-toolkit --global --yes` 후 + `~/.agents/skills/adr-toolkit` 설치 경로 검증, (3) `preflight`/`init`/ + `validate` 전 단계 `ok:true` 검증 구간 추가. (`project-roadmap.md` "Harness + parity", `adapters/cline/README.md` "Verification status") +- [ ] **ADR 중첩/유사도 감지 및 파편화 방지 Eval 시스템** — 작성하려 하거나 기존에 존재하는 ADR 간 내용/주제/영향 범위의 중첩(overlap) 및 파편화를 사전 감지하는 로직과, ADR 집합의 중복·충돌·일관성을 지속해서 평가/검증하는 Evaluation 프레임워크 구축. (유사도 기반 `supersede` 권장, 중복 작성 방지) +- [ ] **개발용 플러그인 메인 저장소 Symlink 자동화 스크립트** — local development 시 `~/.gemini/config/plugins/adr-toolkit`이 임시 워크트리가 아닌 메인 저장소를 항상 바라보도록 하고, 버전 갱신 시 symlink 유효성을 체크하는 도구/가이드 정립. +- [ ] **주간 자동 ADR 헬스체크 및 무효화/깨진 링크 점검 오토메이션** — 주 1회(GitHub Actions Scheduled Workflow 등) 실행되어 오래 방치된 `PROPOSED` ADR, 리팩터링으로 깨진 `affected_paths` 경로, 버전 드리프트를 자동 점검하고 이슈/알림을 생성하는 주간 유지보수 시스템. +- [ ] **PR 시 아키텍처 영향도 자동 판별 및 ADR 작성 유도 (Significance Bot)** — 중요 경계 코드 수정 시 ADR 누락을 PR 체크 단계에서 감지하고 관련 ADR 템플릿과 초안 생성을 자동 가이드하는 CI 봇 연동. +- [ ] **Frontmatter 자동 교정 및 린터 (`adr lint --fix`)** — 사용자가 손으로 수정하며 발생하는 프론트매터 오탈자, 필수 태그/날짜 누락, 들여쓰기 오류 등을 안전하게 자동 보정하는 UX 개선 기능. +- [ ] **대화형 의존성 시각화 뷰어 (`adr graph --format=html`)** — 현 mermaid/json 출력 외에도 단일 HTML/SVG 파일로 ADR 간의 이행/대체/참조 그래프를 브라우저에서 인터랙티브하게 탐색할 수 있는 뷰어 기능. +- [ ] **코드 드리프트 자동 탐지 (`adr drift`)** — ADR 작성 날짜 및 `affected_paths`의 실제 `git log` 변경 이력을 교차 분석하여, ADR 합의 시점 이후 지침과 다르게 변경된 코드 영역을 추적·리포팅하는 서브커맨드. + +- [ ] ~~**파싱 결과 캐시**~~ — **결정: 하지 않음.** 이 CLI는 호출마다 + 새 프로세스라 `functools.lru_cache`는 프로세스 간 재파싱을 전혀 줄이지 + 못하고(원 문제였던 `validate → index → check` 연쇄 재파싱은 별도 + 프로세스 3개), 실제로 벌어지는 "단일 커맨드 내 동일 파일 중복 파싱"도 + 없음을 확인함(search/index/validate/check 전부 파일당 1회 읽기). + 진짜 도움이 되려면 mtime 키 영속 캐시가 필요한데, 이는 staleness 리스크 + 대비 ADR 실사용 규모(수백 개 미만, 감사 보고서 자체 진단)에 비해 + 과한 투자. (감사 보고서 §2.3 3.2) ### Low 두 개의 서로 다른 출처가 섞여 있어 각 항목에 출처를 명시했다. +**출처: `docs/adr-toolkit-audit-report.md`의 🟢 Low 리스크 항목** — 남은 +건 1건뿐: + +- [ ] *(전제조건: Antigravity CLI가 공개 패키지 레지스트리 지원)* + **harness-parity CI에 Antigravity 편입** — `adapters/antigravity/README.md` + 기준 여전히 "Manually verified"뿐, agy 자체가 아직 공개 패키지 레지스트리를 + 지원하지 않음 — 전제조건 미충족. (감사 보고서 §2.1 1.2) + **출처: `docs/enterprise-adoption.md` §4/§6-9** — 코드/아키텍처 감사와는 별개의, 조직 도입·거버넌스 성숙도를 다루는 문서. 아래 항목 대부분은 코드로 "구현"할 수 있는 게 아니라 실제 세계의 전제조건(저장소 public -유지관리자 인원, 저장소 개수)에 막혀 있으니, 시작 전에 전제조건부터 -확인할 것. +전환, 유지관리자 인원, 저장소 개수)에 막혀 있으니, 시작 전에 +전제조건부터 확인할 것. +- [ ] *(전제조건: 저장소 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..bcd7100 100644 --- a/project-roadmap.md +++ b/project-roadmap.md @@ -19,10 +19,17 @@ 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. +- **Automate the Cline CLI adapter install-and-run verification** — Backlog. + `adapters/cline/` is manually verified against Cline CLI 3.0.61 only; the + `harness-parity` job does not yet install Cline and exercise `cline skill add` + + `preflight`/`init`/`validate`. Cline CLI is npm-installable, so the + precondition (public package registry) is met — tracked as a Medium item in + `improvements.md`. Revisit once the npm package name/version-pin and the + install-path verification steps are confirmed. - **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 +50,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/SKILL.md b/skills/adr-toolkit/SKILL.md index 22b08ce..92adc67 100644 --- a/skills/adr-toolkit/SKILL.md +++ b/skills/adr-toolkit/SKILL.md @@ -2,7 +2,7 @@ name: adr-toolkit description: Initialize, record, and check Architecture Decision Records by inspecting the repository and existing decisions before asking questions. user-invocable: true -version: 1.0.1 +version: 1.1.0 --- # ADR Toolkit diff --git a/skills/adr-toolkit/VERSION b/skills/adr-toolkit/VERSION index 7f20734..9084fa2 100644 --- a/skills/adr-toolkit/VERSION +++ b/skills/adr-toolkit/VERSION @@ -1 +1 @@ -1.0.1 \ No newline at end of file +1.1.0 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_cline_adapter.py b/tests/unit/test_cline_adapter.py new file mode 100644 index 0000000..c66c3ec --- /dev/null +++ b/tests/unit/test_cline_adapter.py @@ -0,0 +1,61 @@ +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +CLINE_README = REPO_ROOT / "adapters" / "cline" / "README.md" +SKILL_MD = REPO_ROOT / "skills" / "adr-toolkit" / "SKILL.md" + + +def test_cline_adapter_readme_exists_and_documents_install(): + text = CLINE_README.read_text(encoding="utf-8") + assert "cline skill add" in text + assert "skills/adr-toolkit" in text + assert "SKILL.md" in text + + +def test_cline_adapter_documents_skill_discovery_locations(): + text = CLINE_README.read_text(encoding="utf-8") + assert ".cline/skills" in text + assert "~/.agents/skills" in text + + +def test_cline_entry_file_is_thin_pointer_to_agents_md(): + # Cline joins Codex/Claude/Gemini as a harness with a thin entry file that + # delegates to AGENTS.md. Per-model entry files (DEEPSEEK/GLM/KIMI/QWEN) are + # intentionally not created — those are models routed through a harness, + # not harnesses themselves. + cline_entry = REPO_ROOT / "CLINE.md" + text = cline_entry.read_text(encoding="utf-8") + assert "AGENTS.md" in text + assert "Follow the shared project rules" in text + # Harness entry files must stay thin — no duplication of AGENTS.md rules. + assert "Git Flow" not in text + assert "Release tags" not in text + + +def test_agents_md_lists_cline_among_harnesses(): + text = (REPO_ROOT / "AGENTS.md").read_text(encoding="utf-8") + # Cline must be listed both in the harness enumeration and the entry-file + # paragraph, alongside Codex/Claude/Gemini. + assert "Cline" in text + assert "CLINE.md" in text + # And the per-model-file non-creation policy must be stated. + assert "model/API providers" in text + + +def test_skill_frontmatter_satisfies_cline_requirements(): + # Cline requires `name` to match its directory and `description` (< 1024 + # chars). The canonical package already carries both, so no adapter-local + # manifest is needed for the Cline adapter. + text = SKILL_MD.read_text(encoding="utf-8") + assert "name: adr-toolkit" in text + assert SKILL_MD.parent.name == "adr-toolkit" + # Extract the description line from the YAML frontmatter and confirm it is + # present and under Cline's documented 1024-character limit. + lines = text.splitlines() + desc_lines = [ + line[len("description:"):].strip() + for line in lines + if line.startswith("description:") + ] + assert len(desc_lines) == 1 + assert 0 < len(desc_lines[0]) < 1024 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_readme.py b/tests/unit/test_readme.py index ee53c39..b211b73 100644 --- a/tests/unit/test_readme.py +++ b/tests/unit/test_readme.py @@ -33,3 +33,9 @@ def test_readme_documents_harness_adapters_including_antigravity(): assert "[Antigravity CLI](adapters/antigravity/)" in text assert "adapters/antigravity/README.md" in text + +def test_readme_documents_cline_cli_adapter(): + text = README.read_text(encoding="utf-8") + assert "[Cline CLI](adapters/cline/)" in text + assert "adapters/cline/README.md" in text + 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