Skip to content

Commit 0f8b27a

Browse files
committed
feat(instructions): add mechanism selection guidelines for enforcing architectural constraints
1 parent e4b0015 commit 0f8b27a

2 files changed

Lines changed: 37 additions & 13 deletions

File tree

.github/instructions/go.instructions.md

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ Repository defaults (unless the repository explicitly documents an alternative):
8787

8888
- [GO-LCL-013] `gofmt` is mandatory for formatting (zero-config, non-negotiable).
8989
- [GO-LCL-014] `golangci-lint` is the mandatory meta-linter; configure `.golangci.yml` with a strict baseline.
90+
- [GO-LCL-014a] Enable `depguard` for import-direction constraints (see [GO-CODE-009]).
91+
- [GO-LCL-014b] Enable `gomodguard` for module allow/deny lists (see [GO-CODE-010]).
9092
- [GO-LCL-015] `staticcheck` is **mandatory** for static analysis; run in CI as a blocking gate.
9193
- [GO-LCL-016] `go test` with race detector (`-race`) for tests.
9294

@@ -544,18 +546,31 @@ Per [constitution.md §7](../../.specify/memory/constitution.md#7-code-quality-g
544546
- [GO-CODE-003] Avoid package names like `util`, `common`, `helpers`, `misc` — they become dumping grounds.
545547
- [GO-CODE-004] Use `internal/` for code that should not be imported by other modules.
546548

547-
### 11.2 Naming conventions
549+
### 11.2 Enforcing architectural boundaries with linter rules
550+
551+
When a specification or architecture decision imposes import-direction or dependency constraints, **express them as linter configuration rather than custom Go test code**. Linter rules run on every `make lint`, produce clear error messages, and require no custom test maintenance.
552+
553+
- [GO-CODE-009] Use `depguard` (in `.golangci.yml`) to enforce import restrictions between packages:
554+
- [GO-CODE-009a] Define named rule groups scoping file patterns to allowed/denied import paths.
555+
- [GO-CODE-009b] Include the requirement ID in the `desc` field so failures are self-documenting.
556+
- [GO-CODE-009c] Typical patterns: pipeline isolation (no concrete rule/formatter imports), rule isolation (no sibling rule imports), logger discipline (no stdlib `log` outside the logger package).
557+
- [GO-CODE-010] Use `gomodguard` (in `.golangci.yml`) to enforce module-level bans:
558+
- [GO-CODE-010a] Block deprecated or banned modules with a `reason` referencing the requirement ID.
559+
- [GO-CODE-010b] Use for non-goal enforcement (e.g. banning AI/NLP libraries from an MVP).
560+
- [GO-CODE-011] **Only use custom Go architecture tests** (AST scanning, `go/packages`) for constraints that linter rules cannot express: call-order verification, banned function-call patterns within a package, interface signature checks, or cross-file structural invariants.
561+
562+
### 11.3 Naming conventions
548563

549564
- [GO-CODE-005] Use **MixedCaps** (exported) and **mixedCaps** (unexported). Do not use underscores in Go names.
550565
- [GO-CODE-006] Prefer short, descriptive names. In Go: `i` for loop index, `r` for reader, `w` for writer, `ctx` for context, `err` for error.
551566
- [GO-CODE-007] Avoid stuttering: `user.User` is fine, but `user.UserService` in package `user` should just be `Service`.
552567
- [GO-CODE-008] Acronyms should be all caps: `HTTP`, `URL`, `ID`, not `Http`, `Url`, `Id`.
553568

554-
### 11.3 Struct organisation
569+
### 11.4 Struct organisation
555570

556-
- [GO-CODE-009] Group struct fields logically. Put related fields together.
557-
- [GO-CODE-010] Order fields to minimise padding (largest alignment first), or use `fieldalignment` linter.
558-
- [GO-CODE-011] **Start enums at one, not zero**, unless zero has explicit meaning:
571+
- [GO-CODE-012] Group struct fields logically. Put related fields together.
572+
- [GO-CODE-013] Order fields to minimise padding (largest alignment first), or use `fieldalignment` linter.
573+
- [GO-CODE-014] **Start enums at one, not zero**, unless zero has explicit meaning:
559574

560575
```go
561576
type Status int
@@ -570,9 +585,9 @@ Per [constitution.md §7](../../.specify/memory/constitution.md#7-code-quality-g
570585

571586
This ensures uninitialized values are distinguishable from valid values.
572587

573-
### 11.4 Time handling
588+
### 11.5 Time handling
574589

575-
- [GO-CODE-012] **Always use `time.Duration` for time periods**, never raw integers:
590+
- [GO-CODE-015] **Always use `time.Duration` for time periods**, never raw integers:
576591

577592
```go
578593
// Good
@@ -582,8 +597,8 @@ Per [constitution.md §7](../../.specify/memory/constitution.md#7-code-quality-g
582597
func Retry(attempts int, delay int) error
583598
```
584599

585-
- [GO-CODE-013] Store and transmit times in UTC. Convert to local time only for display.
586-
- [GO-CODE-014] Use `time.Time` for timestamps, not Unix epoch integers.
600+
- [GO-CODE-016] Store and transmit times in UTC. Convert to local time only for display.
601+
- [GO-CODE-017] Use `time.Time` for timestamps, not Unix epoch integers.
587602

588603
---
589604

@@ -838,5 +853,5 @@ These patterns cause recurring issues in Go codebases. Avoid them unless an ADR
838853
839854
---
840855
841-
> **Version**: 1.0.0
842-
> **Last Amended**: 2026-02-08
856+
> **Version**: 1.1.0
857+
> **Last Amended**: 2026-04-26

.github/instructions/includes/quality-gates-baseline.include.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@ Use this shared baseline for quality gate execution expectations. Domain-specifi
1616
- [QG-BASE-DEF-001] Iterate until all checks complete with **no errors or warnings**.
1717
- [QG-BASE-DEF-002] Treat warnings as defects unless explicitly waived in an ADR (with rationale and expiry).
1818

19+
## 3. Mechanism selection 🎯
20+
21+
When a specification or architecture decision imposes a constraint (e.g. "package X must not import package Y", "banned dependency Z"), choose the lightest enforcement mechanism that provides full confidence:
22+
23+
- [QG-BASE-MECH-001] **Linter configuration first.** If the constraint can be expressed as a linter rule (import restrictions, module bans, naming conventions), implement it as linter config. Linter rules run automatically on every `make lint`, produce clear error messages, and need no custom test maintenance.
24+
- [QG-BASE-MECH-002] **Architecture tests second.** Use custom test code (AST scanning, dependency graph analysis) only for constraints that linter rules cannot express: call-order verification, banned function-call patterns within a package, interface signature checks, or cross-file structural invariants.
25+
- [QG-BASE-MECH-003] **Integration/behavioural tests for product behaviour.** Use integration tests for constraints that require running the compiled artefact: CLI exit codes, output formats, error recovery, performance thresholds.
26+
- [QG-BASE-MECH-004] **Do not double up.** If a constraint is enforced by linter config, do not also write a Go test for the same constraint. One mechanism per constraint.
27+
1928
---
2029

21-
> **Version**: 1.0.1
22-
> **Last Amended**: 2026-01-17
30+
> **Version**: 1.1.0
31+
> **Last Amended**: 2026-04-26

0 commit comments

Comments
 (0)