Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,24 @@ Each enforceable guide in `docs/` should have a matching agent in `agents/`. The
| `go-mod-dependency-fix-guide.md` | `go-quality-assistant` |
| `go-makefile-commands.md` | `go-quality-assistant` |
| `go-patterns.md` | `go-quality-assistant` |
| `tdd-guide.md` | `go-test-quality-assistant` |
| `changelog-guide.md` | `agent-auditor` |
| `git-workflow.md` | `agent-auditor` |
| `teamvault-conventions.md` | `go-security-specialist` |
| `go-library-guide.md` | `go-quality-assistant` |
| `test-pyramid-triggers.md` | `go-test-quality-assistant` |
| `go-k8s-binary-conventions.md` | `go-security-specialist` (secret-handling) + `go-quality-assistant` (struct conventions) |
| `markdown-todo-guide.md` | `agent-auditor` |
| `claude-md-guide.md` | `agent-auditor` |
| `k8s-manifest-guide.md` | `go-architecture-assistant` |
| `go-filter-pattern.md` | `go-architecture-assistant` |
| `go-parse-pattern.md` | `go-quality-assistant` |
| `go-validation-framework-guide.md` | `go-quality-assistant` |
| `go-tools-versioning-guide.md` | `go-quality-assistant` |
| `readme-guide.md` | `agent-auditor` |
| `go-boolean-combinator-pattern.md` | `go-architecture-assistant` |
| `python-factory-pattern.md` | `python-architecture-assistant` |
| `adr-guide.md` | `go-architecture-assistant` |

Reference-only docs (patterns, setup guides) don't need agents.

Expand Down
7 changes: 7 additions & 0 deletions docs/adr-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,13 @@ ADRs provide:

**Critical for AI assistants:** ADRs enable AI to understand not just WHAT the architecture is, but WHY it is that way, preventing inappropriate suggestions that conflict with documented decisions.

### RULE adr/required-for-irreversible-architecture-decisions (SHOULD)

**Owner**: go-architecture-assistant
**Applies when**: a PR introduces an irreversible architectural change (database technology choice, message-bus selection, framework swap, major topology change like blue-green vs canary) without a corresponding ADR document under `docs/adr/NNNN-<title>.md` capturing the decision, alternatives considered, rationale, and consequences.
**Enforcement**: judgment (semantic — distinguishing "irreversible architectural decision" from "tactical implementation choice" requires reading the change scope; symptom: PR introduces new top-level package or external dependency without ADR reference)
**Why**: Six months from now, the next contributor will read the code and ask "why did we pick X over Y?" — and the answer needs to be already-written, not "ask Alice, she remembers." ADRs make the decision durable: they capture the alternatives the team rejected, the trade-offs at the time, the constraints that made one option preferable. Without the ADR, future contributors either rediscover the same constraints (waste effort) or quietly revert to the rejected option (waste effort + the original problem reappears). SHOULD because the line between "irreversible" and "tactical" is judgment.

## When to Create an ADR

Create an ADR when:
Expand Down
54 changes: 54 additions & 0 deletions docs/changelog-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,35 @@ Please choose versions by [Semantic Versioning](http://semver.org/).
- fix: Fix WaiterUntil to handle equal times correctly
```

### RULE changelog/preamble-frozen (MUST)

**Owner**: agent-auditor
**Applies when**: a CHANGELOG.md edit inserts content above the `# Changelog` title, modifies the SemVer preamble bullets (MAJOR/MINOR/PATCH), or places a `## Unreleased` / `## vX.Y.Z` section inside (rather than after) the preamble block.
**Enforcement**: judgment (markdown-structure inspection: every CHANGELOG.md must have the canonical preamble first, then sections in `## Unreleased` → `## vX.Y.Z` order)
**Why**: The preamble is the API contract between the changelog and every tool that parses it (dark-factory's version-bump detector, /coding:commit's CHANGELOG validator, downstream release-notes generators). Moving / deleting / shifting it breaks the parsers silently — the next release attempt either bumps the wrong version or misses entries entirely. Restoring is cheap; preventing the edit is cheaper.

#### Bad

```markdown
## Unreleased
- feat: new thing

# Changelog ← preamble shoved below
All notable changes...
```

#### Good

```markdown
# Changelog
All notable changes to this project will be documented in this file.
Please choose versions by [Semantic Versioning](http://semver.org/).
* MAJOR / MINOR / PATCH bullets here

## Unreleased
- feat: new thing
```

**Rules:**
- Preamble with SemVer explanation always present
- **Header is frozen**: everything from the start of the file to the FIRST `##` heading (the `# Changelog` title, the "All notable changes..." line, the SemVer link, and the MAJOR/MINOR/PATCH bullets) MUST NOT be moved, deleted, or have anything inserted above or inside it. Insert `## Unreleased` (or any version section) immediately AFTER the last header line — never before any header line. If the header is incomplete, restore it; never leave it partial.
Expand All@@ -45,6 +74,31 @@ Please choose versions by [Semantic Versioning](http://semver.org/).

## Conventional Prefixes (REQUIRED)

### RULE changelog/conventional-prefix-required (MUST)

**Owner**: agent-auditor
**Applies when**: a bullet under `## Unreleased` in CHANGELOG.md does not start with one of the recognised conventional prefixes (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`, `perf:`).
**Enforcement**: judgment (regex over `## Unreleased` bullets: `^- ([a-z]+:)` first token must be in the allowed prefix set)
**Why**: dark-factory and `/coding:commit` parse the prefix to decide the version bump automatically — any `feat:` entry triggers a minor bump, everything else triggers a patch. Missing or wrong prefix means the version-bump detection fails: the release may patch-bump a feature commit (downstream consumers miss the new functionality in their range queries) or minor-bump a chore. Standardising the prefix is the cheapest possible structure for unambiguous machine parsing.

#### Bad

```markdown
## Unreleased
- Add SpecWatcher ← no prefix
- update go and deps ← no prefix
- fix and refactor ← ambiguous; multiple prefixes
```

#### Good

```markdown
## Unreleased
- feat: Add SpecWatcher to monitor specs/ for approved status changes
- chore: Update Go from 1.25.5 to 1.26.0
- refactor: Extract worktree cleanup to reduce cognitive complexity
```

Every `## Unreleased` entry must start with a conventional prefix:

| Prefix | Meaning | Version bump |
Expand Down
14 changes: 14 additions & 0 deletions docs/claude-code-skill-writing-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,20 @@ Tags: [[Claude Code]] [[Claude Code Plugin System]] [[Claude Code Agent Developm

How to write Claude Code skills — self-contained capabilities that auto-activate based on conversation context.

### RULE skill-writing/scripts-in-scripts-subdir (MUST)

**Owner**: skill-auditor
**Applies when**: a Claude Code skill places executable scripts (`*.sh`, `*.py`) directly alongside `SKILL.md` instead of in a `scripts/` subdirectory.
**Enforcement**: judgment (file-layout check on `skills/<name>/` — only `SKILL.md` at top-level; scripts under `scripts/`)
**Why**: The `scripts/` subdirectory keeps `SKILL.md` discoverable at a glance (one file at top level), groups all executables under a single permission-allowed glob pattern (`Bash(scripts/*.sh)`), and matches the convention every existing bborbe skill follows. Loose-next-to-SKILL.md scripts produce ambiguity ("is this part of the skill or a stray script?") and require enumerating individual files in the skill's `allowed-tools`.

### RULE skill-writing/skill-md-frontmatter-required (MUST)

**Owner**: skill-auditor
**Applies when**: a `skills/<name>/SKILL.md` file is missing the required frontmatter fields — `name:` (must match the directory name) and `description:` (Claude's discovery signal).
**Enforcement**: judgment (YAML-frontmatter inspection: presence of `name` + `description` at the top of every SKILL.md)
**Why**: `description:` is the trigger phrase Claude pattern-matches against conversation context to auto-activate the skill. Without it, the skill is invisible to autonomous discovery — users must type the full `/plugin:skill-name` slash command every time. `name:` is the dispatch key the runtime resolves; mismatch with the directory name produces 404s on invocation. Both fields are cheap to add and break the skill loudly if absent.

## Structure

```
Expand Down
7 changes: 7 additions & 0 deletions docs/claude-md-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,13 @@

Guide for writing CLAUDE.md files. CLAUDE.md is operational context for AI agents working in the codebase — it tells them how to change the code safely.

### RULE claude-md/agent-context-not-user-docs (MUST)

**Owner**: agent-auditor
**Applies when**: a project's CLAUDE.md duplicates README.md user-facing content (install instructions, feature marketing, usage tutorials) instead of serving as terse agent-operational context (build commands, architecture map, constraints).
**Enforcement**: judgment (semantic — distinguishing "agent needs this to change the code" from "user needs this to use it" requires reading the content)
**Why**: CLAUDE.md exists to make AI agents safe + productive in the codebase. Duplicating README content bloats the agent's per-turn context, costs tokens, drowns the actually-load-bearing rules (build commands, ban lists, version-alignment requirements) in marketing copy. Tone signals the audience: README is welcoming and explanatory; CLAUDE.md is terse and imperative. Agents that read CLAUDE.md expect "do this, never that"; users expect "here's what this project does."

## CLAUDE.md vs README.md

| | README.md | CLAUDE.md |
Expand Down
46 changes: 46 additions & 0 deletions docs/git-workflow.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,52 @@

## Hard Rules

### RULE git-workflow/never-direct-commit-to-master (MUST)

**Owner**: agent-auditor
**Applies when**: a git commit lands directly on `master` / `main` without going through a feature branch + PR — typically caught by `pre-push` hook rejecting cross-name pushes to the default branch, or by GitHub's `master-protection` ruleset.
**Enforcement**: judgment + tooling (`~/.git-hooks/pre-push` rejects feature-branch-to-master pushes; GitHub ruleset enforces required PR). Release commits (`release vX.Y.Z`) are the documented exception.
**Why**: Direct-to-master commits skip review, skip CI, skip the audit trail. The 14-commits-on-origin-master trap (PR #1) happened this way: `git worktree add -b feat/foo origin/master` left upstream pointing at master so `git push` shipped to the wrong place. Hook + ruleset catch it before it happens.

#### Bad

```bash
git checkout master && git commit -m "quick fix" && git push # no PR, no review
```

#### Good

```bash
wt-feat coding fix/payment-bug # feature worktree + branch + push -u in one shot
git commit -m "fix: payment bug" && git push
gh pr create # PR triggers review + CI
```

### RULE git-workflow/no-ai-attribution-in-commits (MUST)

**Owner**: agent-auditor
**Applies when**: a git commit message body or trailer contains "Co-Authored-By: Claude", "Generated with Claude Code", "Co-Authored-By: GitHub Copilot", or any other AI-attribution line.
**Enforcement**: judgment (commit-message grep at PR-review time; can be a `commit-msg` hook reject)
**Why**: AI attribution inflates commit metadata noise, signals tool use rather than authorship, and makes commits look "automated" even when the human did substantive design + review work. The human + commit message together constitute the canonical history. AI is a tool; tools don't get authorship credit any more than the editor or compiler does.

#### Bad

```
add login endpoint

Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
```

#### Good

```
add login endpoint

Validates the OAuth token against the configured issuer, returns
the corresponding user record or 401.
```

- **NEVER commit directly to master/main**
- **NEVER add AI attribution** to commits (no "Co-Authored-By: Claude", no "Generated with Claude Code")
- **NEVER use `git -C /path`** — always `cd /path && git ...`
Expand Down
7 changes: 7 additions & 0 deletions docs/go-boolean-combinator-pattern.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,13 @@ Skip this pattern when:
- Decisions need to share state during evaluation (use Chain of Responsibility instead)
- The "decision" returns a value, not a bool (use Strategy or pipeline patterns)

### RULE go-boolean-combinator/result-with-description-not-naked-bool (MUST)

**Owner**: go-architecture-assistant
**Applies when**: a Go boolean-combinator / decision interface's method returns a bare `bool` instead of a `Result` type carrying both the boolean decision AND a human-readable `Description() string` explaining the reason.
**Enforcement**: judgment (interface declaration check: decision-style interfaces (`Check`, `IsTrusted`, `Filtered`, `Allowed`, `Matches`) returning naked `bool`)
**Why**: Combinator decisions show up in audit logs, error messages, and UI tooltips — and "why did this evaluate to false?" is the question every consumer eventually asks. A naked `bool` answers "yes/no"; a `Result` with `Description()` answers "yes/no AND why". `And{}` compositions concatenate child descriptions ("requires X AND requires Y AND not Z"); `Or{}` reports which branch carried the decision. Without the description, debugging "why is this user blocked?" requires reproducing the full decision tree by hand. The cost is one extra method per interface; the value is decisions that explain themselves.

## Components

A boolean combinator pattern has five parts:
Expand Down
7 changes: 7 additions & 0 deletions docs/go-filter-pattern.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,13 @@ Filters are predicates that determine whether data should be included or exclude
3. **Semantic Clarity**: Clear naming that matches user intent
4. **Performance Optimization**: Preprocessing to minimize runtime overhead

### RULE go-filter/document-filtered-semantics (MUST)

**Owner**: go-architecture-assistant
**Applies when**: a Go filter / predicate interface uses ambiguous method names like `Match(item)` / `Check(item)` / `Apply(item)` without doc-comment clarifying whether `true` means "include" or "exclude" — OR uses contradictory naming (`Filtered` returning true for "passes the filter" instead of "filtered out").
**Enforcement**: judgment (interface declaration check: method-name + doc-comment alignment for predicate methods returning bool)
**Why**: Filter semantics inversion is the textbook off-by-true bug — every consumer either gets all the records (filter inverted, treated as pass-through) or zero records (filter inverted, everything excluded). The `Filtered()` convention used in bborbe Go code returns true for "exclude" (the item HAS been filtered out); other codebases use the opposite. Pick one, document it in the interface comment, stick to it everywhere — and never let a new filter type use the opposite semantic in the same codebase.

## Core Filter Interface

```go
Expand Down
14 changes: 14 additions & 0 deletions docs/go-k8s-binary-conventions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,20 @@ Every Go binary that runs in a k8s pod (StatefulSet, Deployment, CronJob, epheme
| `run.CancelOnFirstFinish(ctx, work..., httpServer)` | yes | One goroutine exit cancels the others; clean shutdown |
| Auth via `application` struct fields, not `os.Getenv` | yes | Framework handles defaults + validation + redaction |

### RULE go-k8s-binary/secret-fields-need-display-length (MUST)

**Owner**: go-security-specialist
**Applies when**: an `application` struct field in a Go k8s-deployed binary holds secret material (PEM key, OAuth token, password, API key, JWT signing secret) without the `display:"length"` tag — meaning glog / structured-logging dumps of the application config will print the secret value.
**Enforcement**: judgment (ast-grep follow-up: `struct_field_declaration` with name matching `PEM*` / `*Token*` / `*Secret*` / `*Password*` / `*Key` outside `display:"length"` tag)
**Why**: `argument.Parse()` prints the application config at startup. Without `display:"length"`, the secret value lands in stdout / glog / log aggregators — searchable, indexed, and impossible to redact retroactively once the log batch has shipped. `display:"length"` substitutes `length=42` for the value, preserving the "is it set?" signal without the leak. The tag costs zero runtime; the leak costs a credential rotation.

### RULE go-k8s-binary/argument-struct-not-os-getenv (MUST)

**Owner**: go-quality-assistant
**Applies when**: a Go k8s-deployed binary calls `os.Getenv("FOO")` to read a config / auth value that's already declared as an `application` struct field bound via `argument` tags.
**Enforcement**: judgment (ast-grep partial: `call_expression` matching `os.Getenv(...)` outside `main.go` early bootstrap)
**Why**: `argument.Parse()` already populates the struct field with the right precedence (CLI flag > env var > default), validates `required:"true"` fields at startup, and redacts via `display:"length"`. Calling `os.Getenv` directly duplicates the env-read, skips the validation, and bypasses the redaction (`os.Getenv("PEM_KEY")` returns the raw secret with no `display:"length"` involvement). Use `a.PEMKey` etc.

## Application struct shape

```go
Expand Down
7 changes: 7 additions & 0 deletions docs/go-library-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -94,6 +94,13 @@ ginkgo -v

## 📦 Versioning

### RULE go-library/semver-vprefix-tag-required (MUST)

**Owner**: go-quality-assistant
**Applies when**: a public Go library repo cuts a release without a `git tag` matching `v<MAJOR>.<MINOR>.<PATCH>` (e.g. `v1.0.0`, `v0.12.3`) — either tagless commits, date-based tags (`2026-06-03`), or non-prefixed semver (`1.0.0` without the leading `v`).
**Enforcement**: judgment (git-tag inspection: `git tag --list` filtered against `^v[0-9]+\.[0-9]+\.[0-9]+$`)
**Why**: Go's module system parses tags as `vMAJOR.MINOR.PATCH` — consumers pin to versions via `go get github.com/x/y@v1.2.3`. A tag without the `v` prefix doesn't resolve as a module version; a date-tag doesn't either. Untagged commits force consumers to depend on pseudo-versions (`v0.0.0-20260403114524-913de8870914`), which work but are unreadable and don't survive Go's MVS upgrade logic predictably. The `v` prefix is a hard requirement of `go.mod`'s grammar; semver is the convention Go's module proxy is built on.

Tag releases using semantic versioning:

```bash
Expand Down
7 changes: 7 additions & 0 deletions docs/go-parse-pattern.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,13 @@ Use this pattern when you need:

**Don't use** for simple type assertions where you control the type (use direct type assertion instead).

### RULE go-parse/paired-parse-and-parsedefault (MUST)

**Owner**: go-quality-assistant
**Applies when**: a Go package adds a `ParseX(ctx, value)` function returning `(X, error)` without a paired `ParseXDefault(ctx, value, default) X` that suppresses the error and returns the default — OR vice versa (`ParseXDefault` without `ParseX`).
**Enforcement**: judgment (ast-grep follow-up: `function_declaration` named `Parse*` with `error` return type; pair-check against same-named `*Default` function in the same package)
**Why**: Two call sites consume parsed values: ones where parse failure is a real error worth bubbling (config validation, request parsing) and ones where it's a "fall back to default" condition (optional fields, legacy data with missing keys). Shipping only `ParseX` forces every default-using call site to `if err != nil { use default }` boilerplate; shipping only `ParseXDefault` hides real errors from call sites that need to know. The paired-API convention makes both call patterns one line at the call site and shares the actual parse implementation under the hood.

## Core Pattern Structure

The parse pattern consists of two complementary functions:
Expand Down
Loading
Loading