From f7b8c964068c0047058c31f6d0223a927980a218 Mon Sep 17 00:00:00 2001 From: sonupreetam Date: Wed, 5 Aug 2026 12:47:27 +0200 Subject: [PATCH 1/3] ci: adopt org-infra reusable release workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace inline preflight and GoReleaser jobs with org-infra reusable workflow callers (reusable_release_preflight.yml + reusable_release_goreleaser.yml @ v0.7.1). Key improvements over the inline implementation: - Smart re-run detection (tag at HEAD = re-run, not error) - Semver-aware Python comparator (replaces sort -V which breaks on pre-releases) - Configurable CI checks via ci_checks input - Skip inputs for debugging (skip_semver_check, skip_ci_checks, skip_unreleased_check) - Tag creation via GitHub API (annotated tags) sign-macos job stays inline — extracting that into a reusable is a separate concern. A new check-signing-secrets job provides the has_signing_secrets output that the reusable preflight does not expose. GoReleaser config gains release.extra_files to upload generated Homebrew cask as a release asset (previously done by the inline release job). Ref: https://github.com/unbound-force/unbound-force/issues/428 Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: sonupreetam --- .github/workflows/release.yml | 225 +++++++++------------------------- .goreleaser.yaml | 4 + CHANGELOG.md | 12 ++ 3 files changed, 77 insertions(+), 164 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a80f8b6..8f01f88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,3 +1,20 @@ +# Release +# ======= +# Release pipeline triggered via workflow_dispatch. +# Delegates to org-infra reusable workflows for preflight +# validation and GoReleaser execution with supply chain +# artifacts (cosign signatures and SBOMs). +# +# Preflight validates semver format, tag uniqueness (with +# re-run resilience), semver ordering, CI check status, +# and unreleased commits before creating an annotated tag. +# +# After release, signs macOS archives with Apple Developer +# ID, notarizes them, patches Homebrew cask checksums, and +# pushes to the Homebrew tap. +# +# Fixes: https://github.com/unbound-force/unbound-force/issues/428 + name: Release on: @@ -7,136 +24,58 @@ on: description: 'Release tag (e.g., v0.2.0)' required: true type: string + skip_semver_check: + description: 'Skip semver ordering verification' + type: boolean + default: false + skip_ci_checks: + description: 'Skip CI check verification on HEAD' + type: boolean + default: false + skip_unreleased_check: + description: 'Skip unreleased commits verification' + type: boolean + default: false permissions: {} -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: false - jobs: preflight: - runs-on: ubuntu-latest + name: Preflight + uses: complytime/org-infra/.github/workflows/reusable_release_preflight.yml@0c784711926c9864f027ec565fd7c06a382d80f8 # v0.7.1 + with: + tag: ${{ inputs.tag }} + ci_checks: '["Build and Test"]' + skip_semver_check: ${{ inputs.skip_semver_check }} + skip_ci_checks: ${{ inputs.skip_ci_checks }} + skip_unreleased_check: ${{ inputs.skip_unreleased_check }} permissions: contents: write checks: read - timeout-minutes: 10 - env: - RELEASE_TAG: ${{ inputs.tag }} - outputs: - has_signing_secrets: ${{ steps.check-secrets.outputs.has_signing_secrets }} - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Validate branch - env: - TRIGGER_REF: ${{ github.ref }} - run: | - if [[ "$TRIGGER_REF" != "refs/heads/main" ]]; then - echo "::error::Release must be triggered from main branch, not '$TRIGGER_REF'." - exit 1 - fi - echo "Branch validation passed: triggered from main." - - - name: Validate tag format - run: | - if ! echo "$RELEASE_TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "::error::Invalid tag format: '$RELEASE_TAG'. Must match vMAJOR.MINOR.PATCH (e.g., v0.2.0)." - exit 1 - fi - echo "Tag format valid: $RELEASE_TAG" - - - name: Check tag uniqueness - run: | - REMOTE_REF=$(git ls-remote --tags origin "refs/tags/${RELEASE_TAG}" | awk '{print $1}') - if [ -n "$REMOTE_REF" ]; then - HEAD_SHA=$(git rev-parse HEAD) - if [ "$REMOTE_REF" = "$HEAD_SHA" ]; then - echo "Tag '$RELEASE_TAG' already exists and points to HEAD (re-run case). Continuing." - else - echo "::error::Tag '$RELEASE_TAG' already exists and points to a different commit. Choose a different version." - exit 1 - fi - else - echo "Tag '$RELEASE_TAG' does not exist yet." - fi - - name: Verify semver ordering - run: | - LATEST=$(git tag -l 'v[0-9]*' --sort=-v:refname | head -1) - if [ -z "$LATEST" ]; then - echo "No existing tags found. First release." - exit 0 - fi - echo "Latest existing tag: $LATEST" - # Compare using sort -V: if TAG sorts after LATEST, it is greater - HIGHER=$(printf '%s\n%s' "$LATEST" "$RELEASE_TAG" | sort -V | tail -1) - if [ "$HIGHER" = "$LATEST" ]; then - echo "::error::Tag '$RELEASE_TAG' is not greater than latest release '$LATEST'." - exit 1 - fi - echo "Version ordering valid: $RELEASE_TAG > $LATEST" - - - name: Verify CI passed on HEAD - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - run: | - HEAD_SHA=$(git rev-parse HEAD) - echo "Checking CI status for commit $HEAD_SHA" - - REQUIRED_CHECKS=( - "Build and Test" - ) - - for CHECK_NAME in "${REQUIRED_CHECKS[@]}"; do - STATUS=$(gh api "repos/${GH_REPO}/commits/${HEAD_SHA}/check-runs" \ - --jq ".check_runs[] | select(.name == \"${CHECK_NAME}\") | .conclusion" \ - 2>/dev/null | head -1) - if [ "$STATUS" != "success" ]; then - echo "::error::Required check '${CHECK_NAME}' has not passed (status: ${STATUS:-not found}). Push to main and wait for CI before releasing." - exit 1 - fi - echo " ✓ ${CHECK_NAME}: success" - done - - echo "All required CI checks passed." - - - name: Verify unreleased commits - run: | - LATEST=$(git tag -l 'v[0-9]*' --sort=-v:refname | head -1) - if [ -z "$LATEST" ]; then - COUNT=$(git rev-list --count HEAD) - else - COUNT=$(git rev-list --count "${LATEST}..HEAD") - fi - if [ "$COUNT" -eq 0 ]; then - echo "::error::No unreleased commits since ${LATEST:-initial commit}. Nothing to release." - exit 1 - fi - echo "$COUNT commit(s) since ${LATEST:-initial commit}." - - - name: Create and push tag - run: | - # Skip if tag was already created (e.g., re-run after - # partial failure or manual tag via GitHub API). - if git ls-remote --tags origin | grep -q "refs/tags/${RELEASE_TAG}$"; then - echo "Tag $RELEASE_TAG already exists, skipping creation." - exit 0 - fi - # Annotated tags require a committer identity on the - # CI runner (git tag -a uses GIT_COMMITTER_NAME/EMAIL). - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG" - git push origin "$RELEASE_TAG" - echo "Created and pushed tag: $RELEASE_TAG" + release: + name: Release + needs: preflight + if: needs.preflight.outputs.tag != '' + uses: complytime/org-infra/.github/workflows/reusable_release_goreleaser.yml@0c784711926c9864f027ec565fd7c06a382d80f8 # v0.7.1 + with: + tag: ${{ needs.preflight.outputs.tag }} + permissions: + contents: write + id-token: write + check-signing-secrets: + name: Check Signing Secrets + needs: preflight + if: needs.preflight.outputs.tag != '' + runs-on: ubuntu-latest + permissions: {} + timeout-minutes: 5 + outputs: + has_signing_secrets: ${{ steps.check.outputs.has_signing_secrets }} + steps: - name: Check signing secrets - id: check-secrets + id: check run: | if [ -n "$MACOS_SIGN_P12" ]; then echo "has_signing_secrets=true" >> "$GITHUB_OUTPUT" @@ -146,57 +85,15 @@ jobs: env: MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} - release: - runs-on: ubuntu-latest - needs: preflight - permissions: - contents: write - id-token: write - timeout-minutes: 45 - env: - RELEASE_TAG: ${{ inputs.tag }} - outputs: - has_signing_secrets: ${{ needs.preflight.outputs.has_signing_secrets }} - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - ref: ${{ inputs.tag }} - - - name: Set up Go - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version-file: go.mod - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 - with: - distribution: goreleaser - version: 'v2.14.1' - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GORELEASER_CURRENT_TAG: ${{ inputs.tag }} - - - name: Upload generated cask - run: | - gh release upload "$RELEASE_TAG" \ - --repo "$GITHUB_REPOSITORY" \ - dist/homebrew/Casks/replicator.rb \ - --clobber - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - sign-macos: runs-on: macos-latest - needs: release - if: ${{ needs.release.outputs.has_signing_secrets == 'true' }} + needs: [preflight, release, check-signing-secrets] + if: needs.check-signing-secrets.outputs.has_signing_secrets == 'true' permissions: contents: write timeout-minutes: 30 env: - RELEASE_TAG: ${{ inputs.tag }} + RELEASE_TAG: ${{ needs.preflight.outputs.tag }} steps: - name: Import certificate into Keychain run: | diff --git a/.goreleaser.yaml b/.goreleaser.yaml index cdd7173..527e99a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -30,6 +30,10 @@ archives: checksum: name_template: checksums.txt +release: + extra_files: + - glob: dist/homebrew/Casks/replicator.rb + changelog: sort: asc use: github diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8997f..e1362e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/). +## Unreleased + +### Changed +- adopt-org-infra-release-workflows: Replace inline release preflight + and GoReleaser jobs with org-infra reusable workflow callers + (`reusable_release_preflight` + `reusable_release_goreleaser` @ v0.7.1). + Adds smart re-run detection, semver-aware Python comparator (replaces + `sort -V`), configurable `ci_checks` input, and skip inputs for + debugging. GoReleaser config gains `release.extra_files` for Homebrew + cask upload. `sign-macos` stays inline. + (Part of unbound-force/unbound-force#428) + ## [0.2.0] - 2026-04-06 ### Added From 92e0a4d476295345d4f1784b43bce3a033d09efd Mon Sep 17 00:00:00 2001 From: sonupreetam Date: Wed, 12 Aug 2026 14:49:57 +0200 Subject: [PATCH 2/3] fix: resolve merge conflicts, harden release pipeline and tests - Resolve 24 stash conflict markers across 15 files - Remove workflow-level concurrency (conflicts with reusable preflight) - Add allow_prerelease to release preflight inputs - Add signing credential cleanup step (if: always()) - Add Homebrew cask SHA verification after patching - Add CI workflow header comment (CI-011) - Expand CHANGELOG with Added/Changed/Security sections - Update AGENTS.md convention packs and recent changes - Align CLAUDE.md pack list with AGENTS.md - Strengthen agentkit test assertions and error handling Signed-off-by: sonupreetam --- .github/workflows/ci.yml | 7 + .github/workflows/release.yml | 13 ++ .opencode/agents/reviewer-testing.md | 170 ------------------ .opencode/commands/uf.cobalt-crush.md | 2 +- .opencode/commands/uf.constitution-check.md | 2 +- .opencode/commands/uf.init.md | 3 +- .opencode/commands/uf.unleash.md | 2 +- .opencode/skills/speckit-workflow/SKILL.md | 2 +- .opencode/uf/packs/content.md | 2 +- .opencode/uf/packs/default.md | 2 +- .opencode/uf/packs/go.md | 2 +- .opencode/uf/packs/severity.md | 2 +- .opencode/uf/packs/typescript.md | 2 +- AGENTS.md | 6 + CHANGELOG.md | 31 +++- CLAUDE.md | 4 + internal/agentkit/agentkit_test.go | 36 +++- openspec/schemas/unbound-force/schema.yaml | 2 +- .../schemas/unbound-force/templates/design.md | 2 +- .../unbound-force/templates/proposal.md | 2 +- .../schemas/unbound-force/templates/spec.md | 2 +- .../schemas/unbound-force/templates/tasks.md | 2 +- 22 files changed, 96 insertions(+), 202 deletions(-) delete mode 100644 .opencode/agents/reviewer-testing.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec91cd8..45685f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,10 @@ +# CI +# -- +# Continuous integration pipeline. Runs on push to main and +# pull requests. Validates code quality (vet, govulncheck), +# runs tests with race detection, enforces per-package +# coverage ratchets, and verifies the binary builds. + name: CI on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f01f88..78ce2c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,6 +45,7 @@ jobs: uses: complytime/org-infra/.github/workflows/reusable_release_preflight.yml@0c784711926c9864f027ec565fd7c06a382d80f8 # v0.7.1 with: tag: ${{ inputs.tag }} + allow_prerelease: true ci_checks: '["Build and Test"]' skip_semver_check: ${{ inputs.skip_semver_check }} skip_ci_checks: ${{ inputs.skip_ci_checks }} @@ -86,6 +87,7 @@ jobs: MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} sign-macos: + name: Sign macOS runs-on: macos-latest needs: [preflight, release, check-signing-secrets] if: needs.check-signing-secrets.outputs.has_signing_secrets == 'true' @@ -215,6 +217,11 @@ jobs: ' "$CASK_FILE" > "${CASK_FILE}.patched" mv "${CASK_FILE}.patched" "$CASK_FILE" + if ! grep -q "$ARM64_SHA" "$CASK_FILE"; then + echo "::error::SHA patching failed — new SHA not found in cask file" + exit 1 + fi + git clone "https://x-access-token:${HOMEBREW_TAP_GITHUB_TOKEN}@github.com/unbound-force/homebrew-tap.git" tap cp "$CASK_FILE" tap/Casks/replicator.rb @@ -228,3 +235,9 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + + - name: Cleanup signing materials + if: always() + run: | + security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true + rm -f "$RUNNER_TEMP/cert.p12" "$RUNNER_TEMP/notary_key.p8" diff --git a/.opencode/agents/reviewer-testing.md b/.opencode/agents/reviewer-testing.md deleted file mode 100644 index 8dddb5c..0000000 --- a/.opencode/agents/reviewer-testing.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -description: Test quality and testability auditor ensuring gaze code and specs meet coverage, isolation, and assertion standards. -mode: subagent -model: google-vertex-anthropic/claude-sonnet-4-6@default -temperature: 0.1 -tools: - write: false - edit: false - bash: false ---- - - -# Role: The Tester - -You are a test quality and testability auditor for the gaze project — a Go static analysis tool that detects observable side effects in functions, computes CRAP (Change Risk Anti-Patterns) scores by combining cyclomatic complexity with test coverage, and assesses test quality through contract coverage analysis. - -Your job is to find where tests are shallow, brittle, or missing; where coverage strategy is absent or inadequate; and where acceptance criteria are too vague to verify. You enforce Constitution Principle IV (Testability) and the project's testing conventions. - -**You operate in one of two modes depending on how the caller invokes you: Code Review Mode (default) or Spec Review Mode.** The caller will tell you which mode to use. - ---- - -## Source Documents - -Before reviewing, read: - -1. `AGENTS.md` — Testing Conventions, Coding Conventions, Build & Test Commands -2. `.specify/memory/constitution.md` — Core Principles (especially Principle IV: Testability) -3. The relevant spec, plan, and tasks files under `specs/` for the current work - ---- - -## Code Review Mode - -This is the default mode. Use this when the caller asks you to review code changes. - -### Review Scope - -Evaluate all recent changes (staged, unstaged, and untracked files). Use `git diff` and `git status` to identify what has changed. Focus on test files (`*_test.go`) and the production code they exercise. - -### Audit Checklist - -#### 1. Test Architecture - -- Are tests table-driven where multiple inputs/outputs are being exercised? -- Are test fixtures self-contained in `testdata/src/` directories loaded via `go/packages`? -- Does the test use only the standard `testing` package — no testify, gomega, or external assertion libraries? -- Do test names follow `TestXxx_Description` convention (e.g., `TestReturns_PureFunction`, `TestFormula_ZeroCoverage`)? -- Are test files alongside source in the same directory? Both internal and external package test styles are acceptable. -- Are benchmarks in separate `bench_test.go` files with `BenchmarkXxx` functions? - -#### 2. Coverage Strategy - -- Do tests cover the contract surface (returns, mutations, side effects), not just happy-path line coverage? -- Are observable side effects of the function under test verified — return values, state mutations, I/O operations? -- Is the coverage strategy appropriate for the code's risk level? High-complexity functions (CRAP > 30) need deeper coverage than simple accessors. -- Are acceptance tests named after spec success criteria (e.g., `TestSC001_ComprehensiveDetection`)? - -#### 3. Assertion Depth - -- Do assertions verify specific expected values, not just "no error"? -- Are return values, struct fields, and slice contents checked — not just length or nil/non-nil? -- Are error messages validated when error behavior is part of the contract? -- Do tests use `t.Errorf` / `t.Fatalf` directly — no assertion helpers from third-party packages? - -#### 4. Test Isolation - -- Is there shared mutable state between test cases (package-level variables modified by tests)? -- Do tests depend on execution order? Could they pass individually but fail when run together or in a different order? -- Do tests access external network resources or filesystem state outside the repo? -- Are there tests that depend on timing, wall-clock time, or sleep-based synchronization? - -#### 5. Regression Protection - -- Do tests lock down the behavior that the spec defines as critical? -- Are known-good and known-bad assertion scenarios covered by automated regression tests? -- When a bug was fixed, was a regression test added that would catch the same bug if reintroduced? -- Do JSON schema validation tests exist for JSON output contracts? - -#### 6. Convention Compliance - -- Are tests run with `-race -count=1` compatibility? Are there data races under the race detector? -- Do slow tests (spawning `go test` subprocesses, analyzing the entire module) use `testing.Short()` guards? -- Is output width verified to fit within 80-column terminals where applicable? -- Are test files and source files properly separated — no test code in production files? - ---- - -## Spec Review Mode - -Use this mode when the caller instructs you to review SpecKit artifacts instead of code. - -### Review Scope - -Read **all files** under `specs/` recursively (every feature directory and every artifact: `spec.md`, `plan.md`, `tasks.md`, `data-model.md`, `research.md`, `quickstart.md`, and `checklists/`). Also read `.specify/memory/constitution.md` and `AGENTS.md` for constraint context. - -Do NOT use `git diff` or review code files. Your scope is exclusively the specification artifacts. - -### Audit Checklist - -#### 1. Testability of Requirements - -- Can every acceptance criterion be objectively verified? Flag vague language like "works correctly", "handles gracefully", "is fast", or "is robust" without measurable definition. -- Are acceptance scenarios written in Given/When/Then format with specific, verifiable outcomes? -- Could a developer write failing tests from the spec alone, before any implementation exists? -- Are success criteria technology-agnostic and measurable (specific metrics, counts, percentages)? - -#### 2. Test Strategy Coverage - -- Does the plan define which tests are unit, integration, and e2e? -- Are test file locations and naming patterns specified or inferable from the plan? -- Is the test-to-requirement traceability clear — can you map every task tagged with test work back to a specific requirement? -- Is the TDD approach specified where appropriate (test tasks before implementation tasks)? - -#### 3. Fixture Feasibility - -- Are test fixtures implied by the plan realistic and implementable? -- If `testdata/src/` packages are needed, are they described or do they already exist? -- Are fixture dependencies documented (e.g., Go packages to load, coverage profiles to generate)? -- Could the described fixtures be created without external services or network access? - -#### 4. Coverage Expectations - -- Are coverage ratchet targets specified for new code? -- Are CRAP score thresholds defined or referenced from existing project standards? -- Is there a definition of "sufficient coverage" for this feature — not just "write tests" but measurable criteria? -- Are contract coverage expectations defined (percentage of observable side effects that must be asserted)? - -#### 5. Contract Surface Definition - -- Are the observable side effects of new functions specified clearly enough to write contract tests? -- For each new function or method: are return values, state mutations, and I/O operations documented? -- Could you enumerate the assertion mapping targets from the spec alone? -- Are error conditions and their expected behaviors defined precisely? - -#### 6. Constitution Alignment - -- Does the plan comply with Principle IV: Testability — are functions testable in isolation? -- Does the coverage strategy satisfy Principle IV's MUST requirements (coverage strategy in plan, ratchet enforcement)? -- Is missing coverage strategy flagged as CRITICAL in the spec or plan? (It should be.) -- Are the other three principles (Accuracy, Minimal Assumptions, Actionable Output) also addressed? - ---- - -## Output Format - -For each finding, provide: - -``` -### [SEVERITY] Finding Title - -**File**: `path/to/file:line` (or `specs/NNN-feature/artifact.md` in spec review mode) -**Constraint**: Which test quality dimension is violated -**Description**: What the issue is and why it matters -**Recommendation**: How to fix it -``` - -Severity levels: - -- **CRITICAL**: Missing coverage strategy, untestable requirements, constitution Principle IV violation -- **HIGH**: Vague acceptance criteria, shallow assertions (err == nil only), missing regression tests -- **MEDIUM**: Missing fixture specification, test isolation concerns, convention deviations -- **LOW**: Minor naming convention issues, style improvements, documentation gaps in tests - -## Decision Criteria - -- **APPROVE** only if tests are well-structured, coverage strategy is sound, assertions are deep, tests are isolated, and conventions are followed. -- **REQUEST CHANGES** if you find any test quality issue of MEDIUM severity or above. - -End your review with a clear **APPROVE** or **REQUEST CHANGES** verdict and a summary of findings. diff --git a/.opencode/commands/uf.cobalt-crush.md b/.opencode/commands/uf.cobalt-crush.md index 41e1a5f..e7f4e4a 100644 --- a/.opencode/commands/uf.cobalt-crush.md +++ b/.opencode/commands/uf.cobalt-crush.md @@ -5,7 +5,7 @@ description: > arguments: detects active workflow and runs /speckit.implement or /opsx-apply. --- - + # Command: /uf.cobalt-crush diff --git a/.opencode/commands/uf.constitution-check.md b/.opencode/commands/uf.constitution-check.md index 3986576..d72cd38 100644 --- a/.opencode/commands/uf.constitution-check.md +++ b/.opencode/commands/uf.constitution-check.md @@ -2,7 +2,7 @@ description: "Check a hero constitution's alignment with the Unbound Force org constitution" agent: constitution-check --- - + # Command: /uf.constitution-check diff --git a/.opencode/commands/uf.init.md b/.opencode/commands/uf.init.md index 57c6350..45c00e1 100644 --- a/.opencode/commands/uf.init.md +++ b/.opencode/commands/uf.init.md @@ -5,7 +5,7 @@ description: > correct insertion points. Run after uf init, uf setup, or updating the OpenSpec CLI. --- - + # Command: /uf.init @@ -742,7 +742,6 @@ After processing all customizations, display a summary: ### Legacy Directory Cleanup [status] [item]: [action] ... - ### Summary Applied: N | Already present: N | Errors: N ``` diff --git a/.opencode/commands/uf.unleash.md b/.opencode/commands/uf.unleash.md index 8de6424..81fcaa6 100644 --- a/.opencode/commands/uf.unleash.md +++ b/.opencode/commands/uf.unleash.md @@ -7,7 +7,7 @@ description: > instructions. Exits to the human only when it genuinely needs human judgment. --- - + # Command: /uf.unleash diff --git a/.opencode/skills/speckit-workflow/SKILL.md b/.opencode/skills/speckit-workflow/SKILL.md index ddd90e3..09ffdf6 100644 --- a/.opencode/skills/speckit-workflow/SKILL.md +++ b/.opencode/skills/speckit-workflow/SKILL.md @@ -6,7 +6,7 @@ tags: - workflow - decomposition --- - + # Speckit Workflow — Swarm Skill diff --git a/.opencode/uf/packs/content.md b/.opencode/uf/packs/content.md index 72b1a5d..974d857 100644 --- a/.opencode/uf/packs/content.md +++ b/.opencode/uf/packs/content.md @@ -3,7 +3,7 @@ pack_id: content language: Any version: 1.0.0 --- - + # Convention Pack: Content (Documentation, Blog, PR/Comms) diff --git a/.opencode/uf/packs/default.md b/.opencode/uf/packs/default.md index 513117f..baa47f3 100644 --- a/.opencode/uf/packs/default.md +++ b/.opencode/uf/packs/default.md @@ -3,7 +3,7 @@ pack_id: default language: Any version: 1.0.0 --- - + # Convention Pack: Default (Language-Agnostic) diff --git a/.opencode/uf/packs/go.md b/.opencode/uf/packs/go.md index 605af52..8054d52 100644 --- a/.opencode/uf/packs/go.md +++ b/.opencode/uf/packs/go.md @@ -3,7 +3,7 @@ pack_id: go language: Go version: 1.0.0 --- - + # Convention Pack: Go diff --git a/.opencode/uf/packs/severity.md b/.opencode/uf/packs/severity.md index 31b5f65..1874bf0 100644 --- a/.opencode/uf/packs/severity.md +++ b/.opencode/uf/packs/severity.md @@ -1,7 +1,7 @@ --- description: "Shared severity level definitions for all Divisor Council personas." --- - + # Severity Convention Pack diff --git a/.opencode/uf/packs/typescript.md b/.opencode/uf/packs/typescript.md index 9b1eb42..a656281 100644 --- a/.opencode/uf/packs/typescript.md +++ b/.opencode/uf/packs/typescript.md @@ -3,7 +3,7 @@ pack_id: typescript language: TypeScript version: 1.0.0 --- - + # Convention Pack: TypeScript diff --git a/AGENTS.md b/AGENTS.md index 7ad4269..50f16a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -441,6 +441,7 @@ originally by [Joel Hooks](https://github.com/joelhooks). - Go 1.25+ + cobra (CLI), modernc.org/sqlite (pure Go SQLite), embed (stdlib) (003-rename-terminology) ## Recent Changes +- 428-adopt-org-infra-release-workflows: Adopted org-infra reusable workflows for release pipeline, added govulncheck to CI, added per-package coverage ratchets, added CI convention pack, added SECURITY.md and CODEOWNERS, migrated commands to `uf.*` namespace - 001-go-rewrite-phases: Added Go 1.25+ + `cobra` (CLI), `modernc.org/sqlite` (pure Go SQLite), stdlib `encoding/json` (MCP JSON-RPC), stdlib `os/exec` (git operations) ## Convention Packs @@ -450,6 +451,11 @@ unbound-force. Agents MUST read the applicable pack(s) before writing or reviewing code. - `.opencode/uf/packs/default.md` +- `.opencode/uf/packs/default-custom.md` - `.opencode/uf/packs/severity.md` - `.opencode/uf/packs/content.md` +- `.opencode/uf/packs/content-custom.md` +- `.opencode/uf/packs/ci.md` +- `.opencode/uf/packs/ci-custom.md` - `.opencode/uf/packs/go.md` +- `.opencode/uf/packs/go-custom.md` diff --git a/CHANGELOG.md b/CHANGELOG.md index e1362e6..855e2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,32 @@ this project adheres to [Semantic Versioning](https://semver.org/). ## Unreleased +### Added +- SECURITY.md with vulnerability reporting policy (GitHub Security + Advisories preferred, email fallback) +- CODEOWNERS for governance file protection +- Dependabot configuration for Go modules and GitHub Actions +- `govulncheck` security scanning in CI pipeline (pinned to v1.5.0) +- Per-package coverage ratchets in CI with 11 package thresholds +- `make coverage` and `make check-coverage` Makefile targets +- CI convention pack (`ci.md`) for workflow standards +- Agentkit scaffold tests (`internal/agentkit/agentkit_test.go`) +- CLAUDE.md for Claude Code integration + ### Changed -- adopt-org-infra-release-workflows: Replace inline release preflight - and GoReleaser jobs with org-infra reusable workflow callers - (`reusable_release_preflight` + `reusable_release_goreleaser` @ v0.7.1). - Adds smart re-run detection, semver-aware Python comparator (replaces - `sort -V`), configurable `ci_checks` input, and skip inputs for - debugging. GoReleaser config gains `release.extra_files` for Homebrew - cask upload. `sign-macos` stays inline. +- Release pipeline now uses shared org-infra reusable workflows for + preflight validation and GoReleaser execution. Releases gain supply + chain artifacts (cosign signatures, SBOMs), smarter re-run resilience, + and semver ordering validation. GoReleaser config gains + `release.extra_files` for Homebrew cask upload. macOS code signing + and notarization stays inline. (Part of unbound-force/unbound-force#428) +- Slash commands migrated to `uf.*` namespace (e.g., `/unleash` is + now `/uf.unleash`, `/cobalt-crush` is now `/uf.cobalt-crush`) + +### Security +- Go bumped to 1.25.12 for crypto/tls vulnerability fix +- `govulncheck` pinned to commit SHA for supply chain hygiene ## [0.2.0] - 2026-04-06 diff --git a/CLAUDE.md b/CLAUDE.md index 50965f0..cd0dfaf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,10 +6,14 @@ ## Convention Packs @.opencode/uf/packs/default.md +@.opencode/uf/packs/default-custom.md @.opencode/uf/packs/severity.md @.opencode/uf/packs/content.md +@.opencode/uf/packs/content-custom.md @.opencode/uf/packs/ci.md +@.opencode/uf/packs/ci-custom.md @.opencode/uf/packs/go.md +@.opencode/uf/packs/go-custom.md ## Review Agents (read on-demand) diff --git a/internal/agentkit/agentkit_test.go b/internal/agentkit/agentkit_test.go index b7ed7c6..2f1ecc6 100644 --- a/internal/agentkit/agentkit_test.go +++ b/internal/agentkit/agentkit_test.go @@ -81,9 +81,13 @@ func TestScaffold_SkipsExisting(t *testing.T) { // Pre-create a file that Scaffold would write. forgePath := filepath.Join(dir, ".opencode", "commands", "forge.md") - os.MkdirAll(filepath.Dir(forgePath), 0o755) + if err := os.MkdirAll(filepath.Dir(forgePath), 0o755); err != nil { + t.Fatalf("setup MkdirAll: %v", err) + } original := []byte("# custom content\n") - os.WriteFile(forgePath, original, 0o644) + if err := os.WriteFile(forgePath, original, 0o644); err != nil { + t.Fatalf("setup WriteFile: %v", err) + } results, err := Scaffold(dir, false) if err != nil { @@ -105,7 +109,10 @@ func TestScaffold_SkipsExisting(t *testing.T) { } // Verify file content was NOT overwritten. - data, _ := os.ReadFile(forgePath) + data, err := os.ReadFile(forgePath) + if err != nil { + t.Fatalf("read forge.md after scaffold: %v", err) + } if string(data) != string(original) { t.Errorf("forge.md was overwritten: got %q", string(data)) } @@ -116,9 +123,13 @@ func TestScaffold_ForceOverwrites(t *testing.T) { // Pre-create a file that Scaffold would write. forgePath := filepath.Join(dir, ".opencode", "commands", "forge.md") - os.MkdirAll(filepath.Dir(forgePath), 0o755) + if err := os.MkdirAll(filepath.Dir(forgePath), 0o755); err != nil { + t.Fatalf("setup MkdirAll: %v", err) + } original := []byte("# custom content\n") - os.WriteFile(forgePath, original, 0o644) + if err := os.WriteFile(forgePath, original, 0o644); err != nil { + t.Fatalf("setup WriteFile: %v", err) + } results, err := Scaffold(dir, true) if err != nil { @@ -134,10 +145,17 @@ func TestScaffold_ForceOverwrites(t *testing.T) { } } - // Verify file content WAS overwritten with embedded content. - data, _ := os.ReadFile(forgePath) - if string(data) == string(original) { - t.Error("forge.md was NOT overwritten despite force=true") + // Verify file content WAS overwritten with expected embedded content. + data, err := os.ReadFile(forgePath) + if err != nil { + t.Fatalf("read forge.md after force scaffold: %v", err) + } + expected, err := content.ReadFile("content/commands/forge.md") + if err != nil { + t.Fatalf("read embedded forge.md: %v", err) + } + if string(data) != string(expected) { + t.Errorf("forge.md content after force overwrite doesn't match embedded content:\n got length %d, want length %d", len(data), len(expected)) } } diff --git a/openspec/schemas/unbound-force/schema.yaml b/openspec/schemas/unbound-force/schema.yaml index 9e3a612..4189c1d 100644 --- a/openspec/schemas/unbound-force/schema.yaml +++ b/openspec/schemas/unbound-force/schema.yaml @@ -74,4 +74,4 @@ apply: task as you complete it. Verify that the implementation maintains constitution alignment as documented in the proposal. -# scaffolded by uf vdev +# scaffolded by uf v0.15.0 diff --git a/openspec/schemas/unbound-force/templates/design.md b/openspec/schemas/unbound-force/templates/design.md index 2165de3..3201686 100644 --- a/openspec/schemas/unbound-force/templates/design.md +++ b/openspec/schemas/unbound-force/templates/design.md @@ -17,4 +17,4 @@ ## Risks / Trade-offs - + diff --git a/openspec/schemas/unbound-force/templates/proposal.md b/openspec/schemas/unbound-force/templates/proposal.md index 45d5fa9..bc1a656 100644 --- a/openspec/schemas/unbound-force/templates/proposal.md +++ b/openspec/schemas/unbound-force/templates/proposal.md @@ -54,4 +54,4 @@ output? Does it maintain provenance metadata? --> - + diff --git a/openspec/schemas/unbound-force/templates/spec.md b/openspec/schemas/unbound-force/templates/spec.md index 7b1e55a..343982c 100644 --- a/openspec/schemas/unbound-force/templates/spec.md +++ b/openspec/schemas/unbound-force/templates/spec.md @@ -20,4 +20,4 @@ ### Requirement: - + diff --git a/openspec/schemas/unbound-force/templates/tasks.md b/openspec/schemas/unbound-force/templates/tasks.md index e5bbfc7..9b1ecfb 100644 --- a/openspec/schemas/unbound-force/templates/tasks.md +++ b/openspec/schemas/unbound-force/templates/tasks.md @@ -19,4 +19,4 @@ ## 2. - [ ] 2.1 - + From beaf60b348a7f86bd4fae3b151b4836e94d0bddf Mon Sep 17 00:00:00 2001 From: sonupreetam Date: Wed, 12 Aug 2026 17:50:59 +0200 Subject: [PATCH 3/3] fix: improve error handling in agentkit and update documentation - Distinguish os.ErrNotExist from other os.Stat errors in Scaffold - Wrap os.WriteFile error with context in Scaffold - Add new commands, agents, and skills to CHANGELOG - Broaden Security section to cover all SHA-pinned actions - Add make coverage and make check-coverage to README and CONTRIBUTING Signed-off-by: sonupreetam --- CHANGELOG.md | 19 ++++++++++++++----- CONTRIBUTING.md | 10 ++++++---- README.md | 16 +++++++++------- internal/agentkit/agentkit.go | 8 +++++++- 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855e2f7..b0b10d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,20 @@ this project adheres to [Semantic Versioning](https://semver.org/). - SECURITY.md with vulnerability reporting policy (GitHub Security Advisories preferred, email fallback) - CODEOWNERS for governance file protection -- Dependabot configuration for Go modules and GitHub Actions +- Dependabot configuration for automated weekly dependency update PRs + for Go modules and GitHub Actions - `govulncheck` security scanning in CI pipeline (pinned to v1.5.0) - Per-package coverage ratchets in CI with 11 package thresholds -- `make coverage` and `make check-coverage` Makefile targets +- `make coverage` and `make check-coverage` Makefile targets for + local coverage enforcement before pushing - CI convention pack (`ci.md`) for workflow standards -- Agentkit scaffold tests (`internal/agentkit/agentkit_test.go`) -- CLAUDE.md for Claude Code integration +- CLAUDE.md for automatic convention pack loading in Claude Code +- New agentkit slash commands scaffolded by `replicator init`: + `/forge`, `/forge:status`, `/org`, `/inbox`, `/handoff` +- New agentkit agents: `coordinator`, `worker`, `background-worker` +- New agentkit skills: `always-on-guidance`, `forge-coordination`, + `forge-global`, `learning-systems`, `replicator-cli`, + `system-design`, `testing-patterns` ### Changed - Release pipeline now uses shared org-infra reusable workflows for @@ -32,7 +39,9 @@ this project adheres to [Semantic Versioning](https://semver.org/). ### Security - Go bumped to 1.25.12 for crypto/tls vulnerability fix -- `govulncheck` pinned to commit SHA for supply chain hygiene +- All CI actions and reusable workflows pinned to commit SHAs for + supply chain integrity (`actions/checkout`, `actions/setup-go`, + `complytime/org-infra`, `govulncheck`) ## [0.2.0] - 2026-04-06 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f99a903..65e59b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,12 @@ make check # builds, vets, and runs all tests ## Building and Testing ```bash -make build # Build binary to bin/replicator -make test # Run all tests -make vet # Run go vet -make check # Vet + test (use this before submitting PRs) +make build # Build binary to bin/replicator +make test # Run all tests +make vet # Run go vet +make check # Vet + test (use this before submitting PRs) +make coverage # Run tests with race detection and print coverage +make check-coverage # Enforce per-package coverage ratchets locally ``` ## Testing Conventions diff --git a/README.md b/README.md index e7f3cf6..72d97b3 100644 --- a/README.md +++ b/README.md @@ -172,13 +172,15 @@ docs/ Generated tool reference ## Development ```bash -make build # Build binary to bin/replicator -make test # Run all tests -make vet # Go vet -make check # Vet + test -make serve # Build and run MCP server -make release # GoReleaser dry-run (local) -make install # Install to GOPATH/bin +make build # Build binary to bin/replicator +make test # Run all tests +make vet # Go vet +make check # Vet + test +make coverage # Run tests with race detection and print coverage +make check-coverage # Enforce per-package coverage ratchets locally +make serve # Build and run MCP server +make release # GoReleaser dry-run (local) +make install # Install to GOPATH/bin ``` See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and PR workflow. diff --git a/internal/agentkit/agentkit.go b/internal/agentkit/agentkit.go index fdc76f9..762c4c2 100644 --- a/internal/agentkit/agentkit.go +++ b/internal/agentkit/agentkit.go @@ -6,6 +6,7 @@ package agentkit import ( "embed" + "errors" "fmt" "io/fs" "os" @@ -46,6 +47,8 @@ func Scaffold(targetDir string, force bool) ([]ScaffoldResult, error) { return nil } results = append(results, ScaffoldResult{Path: relPath, Action: "overwritten"}) + } else if !errors.Is(statErr, os.ErrNotExist) { + return fmt.Errorf("stat %s: %w", relPath, statErr) } else { results = append(results, ScaffoldResult{Path: relPath, Action: "created"}) } @@ -61,7 +64,10 @@ func Scaffold(targetDir string, force bool) ([]ScaffoldResult, error) { return fmt.Errorf("read embedded %s: %w", path, readErr) } - return os.WriteFile(destPath, data, 0o644) + if writeErr := os.WriteFile(destPath, data, 0o644); writeErr != nil { + return fmt.Errorf("write %s: %w", relPath, writeErr) + } + return nil }) return results, err