Skip to content

ci: add workflows and rulesets to main - #5

Merged
brettdavies merged 1 commit into
mainfrom
release/ci-workflows-only
Apr 1, 2026
Merged

ci: add workflows and rulesets to main#5
brettdavies merged 1 commit into
mainfrom
release/ci-workflows-only

Conversation

@brettdavies

@brettdavies brettdavies commented Apr 1, 2026

Copy link
Copy Markdown
Owner

Summary

Carried CI workflows and branch protection rulesets from dev to main. This is a non-release infrastructure PR that establishes the CI/CD pipeline and branch governance for the repository.

Type of Change

  • ci: CI/CD configuration changes

Files Modified

Created:

  • .github/workflows/ci.yml
  • .github/workflows/finalize-release.yml
  • .github/workflows/guard-main-docs.yml
  • .github/workflows/guard-main-provenance.yml
  • .github/workflows/release.yml
  • .github/rulesets/protect-dev.json
  • .github/rulesets/protect-main.json

Checklist

  • Code follows project conventions and style guidelines
  • Commit messages follow Conventional Commits
  • Self-review of code completed
  • No new warnings or errors introduced
  • Changes are backward compatible (or breaking changes documented)

Add CI, release, finalize-release, guard-docs, guard-provenance
workflows and branch ruleset snapshots. Enables CI to trigger on
PRs targeting main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@brettdavies
brettdavies merged commit dc26613 into main Apr 1, 2026
2 of 8 checks passed
@brettdavies
brettdavies deleted the release/ci-workflows-only branch April 1, 2026 21:41
brettdavies added a commit that referenced this pull request Apr 30, 2026
#36)

## Summary

Closes the badge-discovery gap from todo `017`: after a passing `anc
check .` run, the CLI now points authors at the
agent-native badge embed snippet for *their* tool, instead of requiring
them to land on the site, navigate to their
scorecard page, and find the embed snippet there. Surface #5 of the
cross-repo badge-surface design (surfaces #1#4
shipped on the site/spec last wave).

End-to-end on this repo (`agentnative-cli`, score 90%):

```text
🏆 Score: 90% — your tool qualifies for the agent-native badge.
   Embed in your README:
     [![agent-native](https://anc.dev/badge/agentnative-cli.svg)](https://anc.dev/score/agentnative-cli)
   Convention: https://anc.dev/badge
```

JSON consumers see the same data as a structured `badge` block —
eligibility, score percent, embed snippet,
scorecard/badge URLs, and the convention pointer.

## Changelog

### Added

- `--output text` now appends an agent-native badge embed hint after the
summary line when the tool clears the 80%
eligibility floor. Below the floor, nothing badge-related is printed
(the convention's "do not nag" rule).
- `--output json` scorecard now includes a `badge` block (`eligible`,
`score_pct`, `embed_markdown`, `scorecard_url`,
`badge_url`, `convention_url`). `embed_markdown` is `null` below the
floor; `scorecard_url` / `badge_url` are
populated whenever a tool slug exists, since the site renders an SVG for
every scored tool.

### Changed

- Bumped scorecard `schema_version` from `"0.4"` to `"0.5"`. Pre-`0.5`
consumers feature-detect the new `badge` key
  and continue to work.

## Type of Change

- [x] `feat`: New feature (non-breaking change which adds functionality)
- [ ] `fix`: Bug fix (non-breaking change which fixes an issue)
- [ ] `refactor`: Code refactoring (no functional changes)
- [ ] `perf`: Performance improvement
- [ ] `docs`: Documentation update
- [ ] `test`: Adding or updating tests
- [ ] `chore`: Maintenance tasks (dependencies, config, etc.)
- [ ] `ci`: CI/CD configuration changes
- [ ] `style`: Code style/formatting changes
- [ ] `build`: Build system changes
- [ ] `BREAKING CHANGE`: Breaking API change (requires major version
bump)

## Related Issues/Stories

- Story: surface #5 of the badge-surface design (todo `017`, local-only
at

`.context/compound-engineering/todos/017-completed-p1-agent-native-badge-hint-on-passing-check.md`)
- Issue: n/a (todo-tracked)
- Architecture: `agentnative-site`
`docs/plans/2026-04-23-002-feat-badge-surface-plan.md` (cross-repo
coordinator for
surfaces #1#4); `agentnative-spec` `feat/badge-claim-convention` branch
(spec-side claim convention)
- Related PRs: surfaces #1#4 (site/spec, already shipped)

## Testing

- [x] Unit tests added/updated
- [x] Integration tests added/updated
- [x] Manual testing completed
- [x] All tests passing

**Test Summary:**

- Unit tests: 443 passing (1 ignored)
- Integration tests: 90 passing (1 network-gated ignored)
- Coverage: no per-line coverage tool configured; pre-push hook (CI
mirror) green — fmt, clippy `-Dwarnings`, full
  suite, cargo-deny, Windows compat

Detail:

- 11 new unit tests in `src/scorecard/mod.rs` cover `compute_badge`
across eligible / below-floor / at-floor / Skips-
excluded / no-scoring-data / empty-slug, plus `BadgeInfo::text_hint` and
`format_text` integration. Full module
  passes 50/50.
- 2 new end-to-end tests in `tests/scorecard_schema_v05.rs`:
`schema_v05_badge_block_reflects_live_tool_slug` (asserts
`badge.*` URLs share the live `tool.name` slug) and
`schema_v05_badge_eligibility_flag_matches_score` (asserts
`eligible == (score_pct >= 80)` and that `embed_markdown` is present iff
`eligible`).
- Schema drift guard updated end-to-end: `tests/integration.rs`
`test_scorecard_json_has_stable_top_level_keys` now
includes `badge` in EXPECTED, still bidirectional ("no unexpected keys"
also fails on accidental additions).

## Files Modified

**Modified:**

- `src/scorecard/mod.rs` — `BADGE_ELIGIBILITY_FLOOR_PCT`,
`BADGE_BASE_URL`, `BadgeInfo` struct, `compute_badge()`,
`BadgeInfo::text_hint()`, `score_pct()` helper, `Scorecard.badge` field,
`format_text` extended to accept
  `Option<&BadgeInfo>`.
- `src/main.rs` — `derive_tool_name()` factored out of `build_tool_info`
(cheap slug derivation, no version probe), used
by the text-mode badge path so the hint and JSON `embed_markdown` always
agree on the slug.
- `tests/integration.rs`, `tests/scorecard_metadata_security.rs` —
`schema_version` assertions bumped to `"0.5"`,
  `badge` added to the stable-keys EXPECTED set.
- `AGENTS.md`, `CLAUDE.md` — documented the `0.5` `badge` block,
eligibility floor, do-not-nag contract, and the
  text-mode hint path.

**Created:**

- (none — every new symbol lives inside an existing module)

**Renamed:**

- `tests/scorecard_schema_v04.rs` → `tests/scorecard_schema_v05.rs`
(drift guard now covers `badge.*` keys).

**Deleted:**

- (none)

## Key Features

- Single source of truth for the badge slug: text-mode and JSON both
pass through `tool.name` (text-mode via the cheap
`derive_tool_name`, JSON via `build_tool_info` → `metadata.tool.name`).
Both call the same `compute_badge` so the
  printed embed snippet and the JSON `embed_markdown` cannot disagree.
- Empty-slug guard: a derivable tool name is required for eligibility.
Without one we can't construct the embed URL
  truthfully — emitting a placeholder would mislead authors. Pinned by
  `compute_badge_empty_slug_is_ineligible_even_at_perfect_score`.
- Score formula matches the site leaderboard exactly: `pass / (pass +
warn + fail)` rounded to integer percent. Skips
and Errors are excluded from both sides of the ratio. Pinned by
`compute_badge_skips_excluded_from_denominator`.

## Benefits

- **Discovery**: a tool author who runs `anc check .` and clears the
floor sees the embed snippet for *their* tool
  without leaving the terminal — no round-trip to the site.
- **Safety by default**: below-floor runs print nothing badge-related.
There is no "you're at 73%, try harder" nag.
- **Consumer feature-detection**: the schema bump is additive. Pre-`0.5`
consumers continue to work; new consumers can
  rely on `badge.*` paths being populated on every `--output json` run.
- **Cross-surface consistency**: the same canonical
`https://anc.dev/badge/<slug>.svg` and `/score/<slug>` URLs render
in both the text hint and the JSON, derived from one tool slug, so a
README link copied from either surface lands on
  the same scorecard.

## Breaking Changes

- [x] No breaking changes
- [ ] Breaking changes described below:

The schema bump from `0.4` to `0.5` is additive. Pre-`0.5` consumers
feature-detect the new `badge` key and continue
to work.

## Deployment Notes

- [x] No special deployment steps required
- [ ] Deployment steps documented below:

The eligibility floor (80%) is hardcoded as a named const
(`BADGE_ELIGIBILITY_FLOOR_PCT`) sourced from the site's
published convention (`agentnative-site/content/badge.md`). When the
spec convention merges off `agentnative-spec`
`feat/badge-claim-convention`, the floor will move into the vendored
spec via `sync-spec` — that is a follow-up,
not a blocker.

## Screenshots/Recordings

Captured by self-dogfooding `cargo run -- check .` against this repo on
the `feat/badge-embed-hint` HEAD.

**Eligible run (text-mode tail) — score 90%, badge hint appended:**

```text
33 checks: 26 pass, 2 warn, 1 fail, 4 skip, 0 error

🏆 Score: 90% — your tool qualifies for the agent-native badge.
   Embed in your README:
     [![agent-native](https://anc.dev/badge/agentnative-cli.svg)](https://anc.dev/score/agentnative-cli)
   Convention: https://anc.dev/badge
```

**Eligible run (`--output json | jq '{schema_version, tool, badge}'`) —
same slug, same URLs, structured fields:**

```json
{
  "schema_version": "0.5",
  "tool": {
    "name": "agentnative-cli",
    "binary": "anc",
    "version": "0.1.0"
  },
  "badge": {
    "eligible": true,
    "score_pct": 90,
    "embed_markdown": "[![agent-native](https://anc.dev/badge/agentnative-cli.svg)](https://anc.dev/score/agentnative-cli)",
    "scorecard_url": "https://anc.dev/score/agentnative-cli",
    "badge_url": "https://anc.dev/badge/agentnative-cli.svg",
    "convention_url": "https://anc.dev/badge"
  }
}
```

**Below-floor run (`anc check . --principle 99 | tail -10`) — no badge
hint, no nag:**

```text
Code Quality
  [FAIL] No .unwrap() in source (code-unwrap)
         /home/brett/dev/agentnative-cli/build.rs:70:20 — path.file_name().unwrap().to_str().unwrap()
         /home/brett/dev/agentnative-cli/build.rs:70:20 — path.file_name().unwrap()
         /home/brett/dev/agentnative-cli/src/skill_install.rs:574:22 — v.to_possible_value().unwrap()
         /home/brett/dev/agentnative-cli/src/skill_install.rs:613:28 — parsed.to_possible_value().unwrap()

1 checks: 0 pass, 0 warn, 1 fail, 0 skip, 0 error
```

Confirms the do-not-nag contract: nothing badge-related appears below
the eligibility floor.

## Checklist

- [x] Code follows project conventions and style guidelines
- [x] Commit messages follow [Conventional
Commits](https://www.conventionalcommits.org/)
- [x] Self-review of code completed
- [x] Tests added/updated and passing
- [x] No new warnings or errors introduced
- [x] Changes are backward compatible (or breaking changes documented)

## Additional Context

The todo flagged three open questions; this PR resolves them:

- **Tool slug source** → derived from `tool.name` (project basename /
command name / binary basename). A future
`--tool-slug` flag can override this if registry slugs ever diverge from
`tool.name`. Out of scope here.
- **Eligibility floor source** → hardcoded const, sourced from the site
convention. Migrates to the vendored spec once
  `agentnative-spec` `feat/badge-claim-convention` merges.
- **Output format gating** → JSON gets the structured `badge` block on
every run; text mode appends a hint only when
  eligible.

UUIDv7 run identifier and content-addressable scorecard hash were both
considered for inclusion in this schema bump
and intentionally deferred — neither has a current consumer (the site
doesn't archive per-run, the badge doesn't pin
to a run, no signing story exists), and pre-launch additive bumps are
cheap precisely so we don't have to pre-add
hooks. Either is a clean additive bump when a consumer materializes.
brettdavies added a commit that referenced this pull request Jun 4, 2026
…tests (#80)

## Summary

Closes a P0 correctness regression in `code-unwrap` surfaced by the
adversarial review of merged PR #77.

PR #77 (commit 817d6fa) introduced a tree-sitter walk that exempts
`.unwrap()` calls inside `#[cfg(test)]`-gated items. The cfg-args
parser, however, treats `not(...)` identically to `any(...)` and
`all(...)`: it descends into the inner argument list without polarity
tracking and returns `true` whenever the bare identifier `test` appears
at any depth. The effect was that `#[cfg(not(test))]` on production-only
code silently exempted that code from the audit, a regression confirmed
both by reading the parser and by an injected adversarial test that
panicked under the buggy logic.

This PR threads a `negated: bool` parameter through
`cfg_args_contain_test`. The bare-test branch returns `true` only at
even parity (`!negated`); at odd parity (under a `not(...)`) the
predicate means production code and the walker keeps scanning siblings
in case another predicate resolves to test-gated. The recursive call for
`not(...)` flips polarity; `any(...)` and `all(...)` preserve it.
Double-negation (`not(not(test))`) correctly restores even parity.

Also corrects the inner-attribute comment block (which claimed sticky
propagation across all siblings while the code actually does one-shot
reset) and pins the intentional asymmetry with a named test.

Per plan #4, the diff is surgical: only the parser function, the
inner-attribute comment, and the test module are touched.

## Changelog

### Fixed

- `code-unwrap`: `#[cfg(not(test))]` no longer silently exempts
production-only code from the audit. The cfg-args parser now tracks
polarity through `not(...)` wrappers; `not(test)`, `any(not(test),
unix)`, and `all(not(test), feature = "x")` correctly classify as
production. Polarity-pinning tests cover the double-negation case
(`not(not(test))`), the `not(any(test))` shape, `cfg_attr(test, …)`
non-gating, and the inner-attribute one-shot reset asymmetry.

## Type of Change

- [x] `fix`: Bug fix (non-breaking change which fixes an issue)

## Related Issues/Stories

- Plan:
`docs/plans/2026-06-03-004-fix-pr77-code-unwrap-cfg-not-test-polarity-plan.md`
- Origin: adversarial review of merged PR #77 (commit 817d6fa)
- Umbrella plan (audit philosophy, supersedes plans #2 and #5):
`docs/plans/2026-06-03-001-refactor-audit-philosophy-structure-over-vocabulary-plan.md`

## Testing

- [x] Unit tests added/updated
- [x] All tests passing

**Test Summary:**

- `cargo test`: 846 passed, 2 ignored (8 suites). 9 new unit tests in
`src/audits/source/rust/unwrap.rs`:
`cfg_not_test_does_not_exempt_production_unwrap`,
`cfg_any_not_test_unix_does_not_exempt`,
`cfg_all_not_test_feature_does_not_exempt`,
`cfg_any_test_or_not_feature_still_exempts`,
`cfg_attr_test_does_not_exempt`,
`inner_attribute_one_shot_consumes_on_use_not_fn`,
`mod_tests_without_gate_does_not_exempt`,
`cfg_not_not_test_does_exempt`, `cfg_not_any_test_does_not_exempt`.
- `cargo clippy --all-targets -- -Dwarnings`: clean.
- Dogfood (`anc audit .`): `code-unwrap` flags one production unwrap at
`src/audits/behavioral/json_errors.rs:192`
(`trimmed.chars().next().unwrap()`). The unwrap has no cfg gate; it
surfaces because PR #79 added it and the audit wasn't re-dogfooded after
that merge. Verified identical behavior on pre-fix `dev` via `git stash`
+ rebuild, ruling out a regression from this PR. Owned by plan #5 (PR
#79 follow-up) or addressable as a one-line fix in a separate PR.
- Dogfood (`anc audit ~/dev/xurl-rs`): `code-unwrap` continues to
`pass`. xurl-rs has zero `#[cfg(not(test))]` items, so the polarity fix
is a no-op against that target.

## Files Modified

**Modified:**

- `src/audits/source/rust/unwrap.rs`: thread `negated: bool` through
`cfg_args_contain_test`; flip polarity on `not(...)` recursion (preserve
on `any`/`all`); rewrite the inner-attribute comment block to match
one-shot reset semantics and name the pinning test; add 9 negative-case
unit tests.

**Created:**

- None.

**Renamed:**

- None.

**Deleted:**

- None.

## Breaking Changes

- [x] No breaking changes

The audit becomes strictly more accurate. Callers that previously relied
on `#[cfg(not(test))]` exempting production code from `code-unwrap` were
doing so accidentally, since that path was an unintended consequence of
PR #77's missing polarity tracking.

## Deployment Notes

- [x] No special deployment steps required

## Checklist

- [x] Code follows project conventions and style guidelines
- [x] Commit messages follow Conventional Commits
- [x] Self-review of code completed
- [x] Tests added/updated and passing
- [x] No new warnings or errors introduced
- [x] Changes are backward compatible
brettdavies added a commit that referenced this pull request Jun 4, 2026
… platform verbs (#83)

## Summary

Follow-up to merged PR #76 addressing two coupled gaps surfaced during
the post-merge adversarial review.

`.anc.toml [p6] domain_verbs` lets any CLI declare its own subcommand
vocabulary as "standard," but the scorecard row gave no signal whether
the Pass was assisted. A domain-verb-assisted Pass was byte-identical in
JSON to a built-ins-only Pass. Separately, of the 18 verbs PR #76 added
to `STANDARD_VERBS`, 7 are X/Mastodon-specific (`post`, `repost`,
`unrepost`, `quote`, `like`, `unlike`, `dm`). Their inclusion encoded
one platform's vocabulary as universal, and let any social CLI Pass
without exercising the `.anc.toml` opt-in PR #76 introduced.

This PR adds scorecard transparency for domain-verb-assisted Passes and
trims the platform-specific verbs out of the built-in list. Schema bumps
`0.7 → 0.8` (additive).

## Changelog

### Added

- Schema `0.8`: `using_domain_verbs: Option<bool>` and
`domain_match_count: Option<usize>` on each `AuditResultView` row.
Populated when `p6-standard-names` Passes via `.anc.toml [p6]
domain_verbs` recognition; absent (not `null`) for rows that did not
consult `domain_verbs`.
- `MitigationInfo` carrier on `AuditResult` (typed, audit-agnostic)
tracking built-in vs domain match counts and the first 5 matched
domain-verb names in encounter order.
- Pass row's `evidence` field is populated with the built-in vs domain
ratio when assisted by `domain_verbs`, e.g. `"7/8 subcommands standard
(3 via .anc.toml [p6].domain_verbs: [post, like, repost])"`. Rows
without mitigation keep the historical `evidence: null` on Pass.

### Changed

- `STANDARD_VERBS` no longer recognizes `post`, `repost`, `unrepost`,
`quote`, `like`, `unlike`, `dm`. Social and platform-specific CLIs that
previously Passed via built-ins must now declare these in `.anc.toml
[p6] domain_verbs` (documented).
- `audit_standard_names` returns a `StandardNamesResult` struct (status
+ optional `MitigationInfo`) instead of bare `AuditStatus`.
- Warn evidence on `p6-standard-names` includes a pointer to
`docs/solutions/architecture-patterns/anc-toml-domain-verbs-pattern-2026-06-03.md`
so authors discover the opt-in.
- `CLAUDE.md` schema history block updated to document the `0.6`, `0.7`,
and `0.8` additions (the inline history had drifted at `0.5`).

### Fixed

- Audit-identity collapse from PR #76: a `.anc.toml`-assisted Pass is
now distinguishable in the scorecard JSON from an unassisted one.
- Verb-list dilution from PR #76: platform-specific vocabulary no longer
inflates the cross-CLI built-in list.

## Type of Change

- [x] `fix`: Bug fix (non-breaking change which fixes an issue)

## Related Issues/Stories

- Plan:
`docs/plans/2026-06-03-003-fix-pr76-domain-verbs-scorecard-transparency-plan.md`
- Origin: adversarial review of merged PR #76 (commit 9263c5c).
- Umbrella plan (audit philosophy, supersedes plans #2 and #5 at the
design level):
`docs/plans/2026-06-03-001-refactor-audit-philosophy-structure-over-vocabulary-plan.md`
- Pattern documentation:
`docs/solutions/architecture-patterns/anc-toml-domain-verbs-pattern-2026-06-03.md`
(already committed and pushed to the symlinked `solutions-docs` repo,
commit `402354b`).

## Testing

- [x] Unit tests added/updated
- [x] Integration tests updated
- [x] All tests passing

**Test Summary:**

- `cargo test`: 855 passed, 2 ignored (8 suites). Net +7 from baseline:
5 new R5 adversarial tests in `audit_standard_names`, 2 new
format-evidence tests pinning the 5-example truncation contract.
- `cargo clippy --all-targets -- -Dwarnings`: clean.
- `cargo fmt --check`: clean.
- `anc emit coverage-matrix --check`: clean.
- Dogfood: `anc audit . --output json | jq '.results[] | select(.id ==
"p6-may-standard-names")'` returns `status: "pass"` with
`using_domain_verbs` and `domain_match_count` absent from the row keys
(`anc` itself has no `.anc.toml`).

## Files Modified

**Modified:**

- `src/types.rs`: new `MitigationInfo` struct; new optional `mitigation:
Option<MitigationInfo>` field on `AuditResult`.
- `src/audits/behavioral/standard_names.rs`: refactored
`audit_standard_names` to return `StandardNamesResult`; bifurcated
built-in vs domain match counting; populates `MitigationInfo` on
domain-assisted Pass; trimmed 7 platform-specific verbs from
`STANDARD_VERBS`; appends docs URL to Warn evidence; 13 unit tests (8
updated to the new return shape + 5 new adversarial + 2 format-evidence
pinning tests).
- `src/audits/behavioral/mod.rs`: made `standard_names` `pub(crate)` so
the scorecard view layer can call `format_pass_evidence`.
- `src/scorecard/mod.rs`: `SCHEMA_VERSION` bumped to `"0.8"`;
`AuditResultView` gains `using_domain_verbs` and `domain_match_count`;
`from_row` reads `r.mitigation`, populates the fields, and synthesizes
Pass evidence prose via `format_pass_evidence`.
- `schema/scorecard.schema.json`: `$id` pinned to `scorecard-v0.8`.
- `tests/standard_names_integration.rs`: fixture retuned to `archive` +
`follow` + `mentions` so the `.anc.toml` loader is genuinely exercised
after the platform-verb trim.
- `tests/integration.rs`, `tests/scorecard_metadata_security.rs`,
`tests/scorecard_schema_v05.rs`: schema-version assertions bumped to
`"0.8"`; the v0.7 → v0.8 schema `$id` rename pinned by
`schema_v05_target_path_carries_no_separators` regression test.
- `CLAUDE.md`: schema history block updated (was stuck at `0.5`); new
`MitigationInfo` semantics block mirrors the existing per-version
addition pattern.
- ~77 audit and test files: mechanical `mitigation: None` insert in
every `AuditResult { ... }` constructor. This is the unavoidable churn
of adding a typed transparency field to the canonical result carrier.
`cargo check` verified the sed hit only valid sites.

**Created:**

- None.
(`docs/solutions/architecture-patterns/anc-toml-domain-verbs-pattern-2026-06-03.md`
lives in the symlinked `solutions-docs` repo and was committed there
separately.)

**Renamed:**

- None.

**Deleted:**

- None.

## Breaking Changes

- [x] No breaking changes (schema `0.8` is additive)

The 7 platform verbs removed from `STANDARD_VERBS` are a behavior
tightening, not a contract break. CLIs that relied on them being
built-ins were exploiting PR #76's overly broad vocabulary; the
remediation path is `.anc.toml [p6] domain_verbs` (documented).

## Deployment Notes

- [x] No special deployment steps required

## Checklist

- [x] Code follows project conventions and style guidelines
- [x] Commit messages follow Conventional Commits
- [x] Self-review of code completed
- [x] Tests added/updated and passing
- [x] No new warnings or errors introduced
- [x] Changes are backward compatible
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant