feat(architecture): bootstrap 6 rule blocks in go-architecture-patterns#16
Conversation
Restructures the 6 enforceable conventions in docs/go-architecture-patterns.md into canonical `### RULE` blocks. Mirrors PRs #2-5, #8, #10, #14, #15. Rules added (rules/index.json: 50 -> 56, new go-architecture/* family): - go-architecture/counterfeiter-directive-on-interface (MUST) Every substitutable interface needs a //counterfeiter:generate directive so go generate regenerates the fake when the interface drifts. - go-architecture/new-prefix-constructor-naming (MUST) Constructors start with New (not Create/Make/etc). Partial overlap with go-factory/no-impl-in-factory-pkg for Create*. - go-architecture/constructor-returns-interface (MUST) New* returns the interface, not *concrete struct. Hides implementation, keeps dependency direction one-way. - go-architecture/private-struct-matches-interface (SHOULD) UserService -> userService (first letter lowered). godoc pairs them; IDE outlines pair them; refactors find them with one rename. - go-architecture/no-globals-or-singletons (MUST) Service deps via constructor injection, never package-level vars. Globals break parallel tests, hide the dep graph, make refactors fragile. - go-architecture/business-logic-not-in-main (MUST) main.go is wiring; domain operations live in pkg/. Mixed main.go is untestable and unreachable from other binaries. Cross-references to existing rules (already in index, not duplicated): - go-errors/no-context-background-in-business-logic — covers context discipline - go-context/cancel-check-in-loop — covers infinite-loop ctx.Done() checks - go-time/* family — covers libtime injection All examples generic (User, UserService, Worker, log/db deps). No trading-domain terms (KafkaBrokers grep hit is server infrastructure, not trading-broker terminology — pre-existing in main.go example). No personal vault paths. Pre-emptive checks (lessons from PRs #6, #8, #14, #15): clean grep, unique rule IDs (6 new vs 50 existing), go-architecture-assistant agent exists at agents/go-architecture-assistant.md, make build-index regenerated, check-index passes.
There was a problem hiding this comment.
PR Review: feat(architecture): bootstrap 6 rule blocks in go-architecture-patterns
Pre-commit & Automated Checks
- make precommit: PASS
- LICENSE file: Present
- Diff: 2 files changed (+247 lines total: +193 docs, +54 rules/index.json)
Consolidated Report
Must Fix (Critical)
None — no security vulnerabilities or correctness bugs in the diff (this is a documentation-only change).
Should Fix (Important)
-
rules/index.json:77-79—counterfeiter-directive-on-interfaceenforcement field usesnot.precedesas an ast-grep operator, but this does not exist in ast-grep's pattern syntax. The negative precedence relation cannot be expressed this way. This is a factual error that would mislead anyone implementing the mechanical layer. -
docs/go-architecture-patterns.md:64-68—new-prefix-constructor-namingapplies_whendoes not exemptCreate*factory functions orpkg/factory/locations. The doc's own section 3 (line 344) says "Create*prefix for factories". A mechanical enforcement of this rule would false-positive on everyCreateUserServiceinpkg/factory/. The enforcement note at line 96 acknowledges "partial overlap" but does not state which rule ownsCreate*. -
docs/go-architecture-patterns.md+rules/index.json— Rules 1 (counterfeiter-directive-on-interface), 3 (constructor-returns-interface), and 4 (private-struct-matches-interface) form a coupled three-part pattern (interface → constructor → struct) but are presented as independent rules with no cross-references. A developer reading one in isolation may not understand the full intent, and theapplies_whenfields give no indication they must be evaluated together. -
docs/go-architecture-patterns.md:142-147—private-struct-matches-interfaceenforcement is described as "paired-declaration check" — a term not used elsewhere in the index. Other rules name concrete ast-grep kinds (e.g.,var_spec,function_declaration). The vague label makes it harder to implement the mechanical follow-up. -
docs/go-architecture-patterns.md:555-560—no-globals-or-singletonsapplies_whendescribes "package-levelvar" but does not coverinit()functions orsync.Oncelazy singletons, which share the same test-ordering and parallelism problems described in thewhysection. At MUST level, the trigger should be complete or explicitly scoped. -
docs/go-architecture-patterns.md:791-796—business-logic-not-in-mainat MUST level has no mechanical enforcement path. Other judgment-only MUST rules at least provide a coarse ast-grep filter. This rule provides none, making it the least actionable MUST in the set. Additionally, theapplies_whencoversapplication.Runbut does not exclude*_test.gofiles that might exercise that method. -
agents/go-architecture-assistant.md+CLAUDE.md— All 6 new rules listgo-architecture-assistantasowner, but the CLAUDE.md "Doc ↔ Agent Alignment" table mapsgo-architecture-patterns.md→go-quality-assistant. The agent scope (agents/go-architecture-assistant.md lines 236–252) does not list naming conventions, counterfeiter directives, or globals/singletons as owned concerns. This is a doc-agent alignment gap introduced by the new rules.
Nice to Have (Optional)
docs/go-architecture-patterns.md:73-78—new-prefix-constructor-namingbad example labelsCreateUserServiceas a "Constructor" butCreate*is the factory prefix per section 3.docs/go-architecture-patterns.md:820-831—business-logic-not-in-maingood example showsreturn service.ProcessUsers(ctx)with no error return, inconsistent with the doc'serrors.Wrappattern used everywhere else.docs/go-architecture-patterns.md:580-597—no-globals-or-singletonsgood example introducesWorker/DoWork— first departure from the doc's consistentUserService/Userexamples.docs/go-architecture-patterns.md:145—private-struct-matches-interfacebad example shows 3 structs but theapplies_whenfires on single-implementation packages only.docs/go-architecture-patterns.md:791—business-logic-not-in-mainrule block is in section 11 (Antipatterns) rather than section 2 (main.go Pattern) whereapplication.Runis defined.
Next Steps
- The
not.precedesast-grep syntax error inrules/index.jsonis the most clear-cut fix — replace with a valid ast-grep expression or remove the inline hint. - The
new-prefix-constructor-namingpkg/factory/boundary clarification is needed before this rule can be enforced consistently. - Consider grouping the three coupled rules (counterfeiter, constructor-returns, private-struct) with cross-references so they are evaluated as a unit.
{
"verdict": "request-changes",
"summary": "Six new architecture RULE blocks bootstrapped in go-architecture-patterns.md with matching rules/index.json entries. The rules are structurally sound and well-written, but have 7 should-fix issues: a factually wrong ast-grep operator (not.precedes), missing pkg/factory/ boundary in the constructor-naming rule, an incomplete trigger in no-globals-or-singletons, a MUST-level rule with no mechanical enforcement path, and a doc-agent alignment gap where all 6 rules claim go-architecture-assistant as owner but the agent's scope and CLAUDE.md's mapping table don't cover them. No critical issues; this is a documentation-only change.",
"comments": [
{
"file": "rules/index.json",
"line": 77,
"severity": "major",
"message": "go-architecture/counterfeiter-directive-on-interface enforcement uses 'not.precedes' which is not a valid ast-grep operator. The negative precedence relation cannot be expressed this way — this is a factual error that would mislead mechanical enforcement."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 67,
"severity": "major",
"message": "go-architecture/new-prefix-constructor-naming applies_when does not exempt Create* factory functions or pkg/factory/ locations. The doc's section 3 permits Create* for factories, but this rule would false-positive on them. Enforcement note acknowledges 'partial overlap' but does not assign ownership."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 19,
"severity": "major",
"message": "go-architecture/counterfeiter-directive-on-interface applies_when uses 'intended to be substitutable' — this requires intent-judgment by the agent with no concrete trigger. Consider scoping to interfaces referenced by *_test.go files."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 145,
"severity": "major",
"message": "go-architecture/private-struct-matches-interface applies_when fires on 'single struct' implementations, but the enforcement description ('paired-declaration check') is vague compared to other rules that name concrete ast-grep kinds. The bad example shows 3 structs when the trigger only covers single-impl packages."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 557,
"severity": "major",
"message": "go-architecture/no-globals-or-singletons applies_when covers package-level var but omits init() functions and sync.Once patterns, which share the same test-ordering and parallelism problems. At MUST level the trigger should be complete."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 794,
"severity": "major",
"message": "go-architecture/business-logic-not-in-main at MUST level has no mechanical enforcement path — no coarse ast-grep filter unlike other judgment-only rules. Also lacks *_test.go exclusion for application.Run."
},
{
"file": "rules/index.json",
"line": 65,
"severity": "major",
"message": "Doc-agent alignment gap: all 6 new rules list go-architecture-assistant as owner, but agents/go-architecture-assistant.md scope (lines 236-252) does not cover these patterns, and CLAUDE.md maps go-architecture-patterns.md to go-quality-assistant. Agent scope needs updating."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 73,
"severity": "nit",
"message": "go-architecture/new-prefix-constructor-naming bad example labels CreateUserService as a 'Constructor' — Create* is the factory prefix per section 3 of the same doc."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 830,
"severity": "nit",
"message": "go-architecture/business-logic-not-in-main good example returns nil without errors.Wrap, inconsistent with the doc's error-wrapping pattern used everywhere else."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 585,
"severity": "nit",
"message": "go-architecture/no-globals-or-singletons good example uses Worker/DoWork — first departure from the doc's consistent UserService/User examples throughout."
}
],
"concerns_addressed": [
"correctness: ast-grep enforcement syntax error in rules/index.json raised as major comment",
"correctness: subjective triggers in applies_when raised as major comments (counterfeiter 'intended to be substitutable', new-prefix missing pkg/factory/ boundary)",
"correctness: no automated validation of rule definitions — no change in this PR, but the concern is acknowledged: the build-index.py extraction is working correctly (confirmed by make precommit pass)",
"tests: no test coverage for rules — no change in this PR; rules/index.json is a JSON data file, not Go code, so go test coverage is not applicable"
]
}7 MAJOR + 3 NIT findings from the bot — all valid, all fixed: MAJOR: - counterfeiter-directive-on-interface: removed invalid 'not.precedes' ast-grep claim. Real path is the PR #11 surrounding-comment recipe; agent handles the absence check. - counterfeiter-directive-on-interface: scoped applies_when to a concrete trigger ('exported interfaces in non-main packages with *_test.go importing mocks') instead of the intent-based 'intended to be substitutable'. - new-prefix-constructor-naming: exempted pkg/factory/** explicitly (Create* is the factory prefix per go-factory rule). Cross-reference added; the Bad example drops the CreateUserService case (it's a factory, not a service constructor). - private-struct-matches-interface: tightened to single-impl trigger in the applies_when (was 'a struct'; now 'exactly one struct implementing every method'). Bad example reduced to one mismatch (was showing three alternatives, conflicting with the trigger). - no-globals-or-singletons: applies_when now covers init() and sync.Once patterns alongside package-level vars. Same test-ordering and parallelism problems; MUST-level rule needs the complete trigger. - business-logic-not-in-main: explicit *_test.go exemption added; coarse ast-grep filter described (main.go importing bborbe/errors/validation = strong signal). - doc-agent alignment: CLAUDE.md table now maps go-architecture-patterns.md to both go-architecture-assistant (the new rule-block owner) and go-quality-assistant (broader review). Reflects the agent's cross-unit-concerns scope and resolves the bot's alignment gap. NIT: - new-prefix Bad example mislabeled CreateUserService as 'Constructor' — Create* is the factory prefix per section 3. Removed that case. - business-logic-not-in-main Good example added errors.Wrap to match the doc's error-wrapping pattern throughout. - no-globals-or-singletons Good example: Worker -> UserService for consistency with the doc's running example. make build-index regenerated; check-index passes.
superseded by new automated review
There was a problem hiding this comment.
auto-approve disabled for this repo, review submitted as comment
PR Review: feat(architecture): bootstrap 6 rule blocks in go-architecture-patterns
Automated Checks
| Check | Result |
|---|---|
| LICENSE file | ✅ Exists |
make precommit |
|
| JSON syntax (plugin.json, marketplace.json, rules/index.json) | ✅ All valid |
Agent agents/go-architecture-assistant.md |
✅ Exists |
| Rule anchor cross-references | ✅ All 4 referenced rules exist |
Plan Concerns — Resolution Status
| Concern | Status |
|---|---|
CLAUDE.md table references go-architecture-assistant but agent not in repo |
✅ Addressed — agents/go-architecture-assistant.md exists |
All 6 rules use judgment enforcement (no ast-grep) |
✅ Acknowledged — correctly documented in each rule |
| Cross-referenced rule IDs exist in index | ✅ Addressed — go-errors/no-context-background-in-business-logic, go-context/cancel-check-in-loop, go-time/*, go-factory/no-impl-in-factory-pkg all verified present |
go-factory/no-impl-in-factory-pkg exists for exclusion scope |
✅ Addressed — rule confirmed at line 237 |
ast-grep 0.43.0 limitation on counterfeiter-directive-on-interface |
✅ Acknowledged — stated in enforcement field |
Consolidated Report
Must Fix (Critical)
None.
Should Fix (Important)
-
docs/go-architecture-patterns.md:74-76—new-prefix-constructor-namingBad example is now underillustrative
The edit removedCreateUserService(rightly, to avoid overlapping with thepkg/factory/**exception), leaving onlyMakeUserServiceandUserSvc. The "Why" section says "every consumer a second of 'wait, is this the constructor?'" but the example shows only two variants. Adding one more (e.g.BuildUserService) restores the breadth without reintroducing the overlap.
Bucket:Should Fix -
docs/go-architecture-patterns.md:578-593—no-globals-or-singletonsGood example usesUserServicewith aDoWorkmethod — semantic mismatch
The interface was renamed fromWorkertoUserServicein this example, butDoWorkis not a method name consistent withUserService's CRUD identity used everywhere else in the doc (Create,Get). A reader scanning quickly may think the doc has an inconsistency. RenameDoWorktoCreateto match the interface identity, or revert the interface name toWorker.
Bucket:Should Fix
Nice to Have (Optional)
-
docs/go-architecture-patterns.md:153-154—private-struct-matches-interfaceBad example shows only one anti-pattern variant
The "Why" enumerates three forms (defaultUserService / userServiceImpl / internalUser) but the code block shows onlyuserServiceImpl. The other two variants were dropped without explanation. Restoring one or two lines matches code to prose.
Bucket:Nice to Have -
docs/go-architecture-patterns.md:821-824—business-logic-not-in-mainGood example'serrors.Wrapatapplication.Runboundary is unexplained
Theerrors.Wrap(ctx, err, "process users failed")inapplication.Runis wiring-level context, not domain logic — but a strict reader might move it intopkg/, which is the wrong direction. A one-line comment (// errors.Wrap at the application boundary is wiring-level context, not domain logic) removes the ambiguity.
Bucket:Nice to Have -
docs/go-architecture-patterns.md:19—counterfeiter-directive-on-interfaceApplies-when clause is circular for new packages
The rule triggers only when*_test.gofiles already importmocks. A brand-new package's first commit escapes the rule; the developer only gets flagged after writing tests. The fix is to drop the test-import prerequisite and apply the rule to all exported interfaces in non-mainservice packages.
Bucket:Nice to Have -
docs/go-architecture-patterns.md:82-88—new-prefix-constructor-namingGood example implicitly relies onconstructor-returns-interfacewithout cross-reference
TheNewUserService(...) UserService { ... }example returns an interface type, which is governed by the next rule block. A reader who reads only this rule gets a Good example that looks arbitrary. Add a parenthetical or a cross-reference in the "Why" section.
Bucket:Nice to Have
Summary
The PR correctly bootstraps 6 well-structured rule blocks into go-architecture-patterns.md, updates the rules/index.json with matching entries and correct anchors, and updates CLAUDE.md to assign ownership to the existing go-architecture-assistant. No code was changed — this is a docs-only PR. Two documentation-quality issues in the examples (underillustrative Bad examples, semantic mismatch in UserService/DoWork) are worth fixing before merge; the remaining issues are optional polish.
{
"verdict": "approve",
"summary": "PR correctly bootstraps 6 rule blocks with proper index entries and agent ownership. All plan concerns were addressed: the go-architecture-assistant agent exists, all cross-referenced rules are confirmed in index.json, and the judgment-only enforcement is accurately documented. Two example-quality issues (underillustrative Bad code, UserService/DoWork semantic mismatch) are worth fixing; remaining findings are optional polish.",
"comments": [
{
"file": "docs/go-architecture-patterns.md",
"line": 76,
"severity": "major",
"message": "Should Fix: new-prefix-constructor-naming Bad example shows only 2 variants (MakeUserService, UserSvc) after CreateUserService was removed to avoid factory-pattern overlap. The Why section implies broader coverage — add BuildUserService or similar to restore illustrative breadth."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 591,
"severity": "major",
"message": "Should Fix: no-globals-or-singletons Good example renamed interface to UserService but keeps DoWork method — semantic mismatch with UserService's CRUD identity (Create, Get) used everywhere else in the doc. Rename DoWork to Create to match, or revert interface name to Worker."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 154,
"severity": "nit",
"message": "Nice to Have: private-struct-matches-interface Bad example shows only userServiceImpl but the Why section names three variants (defaultUserService, userServiceImpl, internalUser). Restore at least one more variant to match prose to code."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 824,
"severity": "nit",
"message": "Nice to Have: business-logic-not-in-main Good example uses errors.Wrap in application.Run — correct but unexplained. A reader might move it into pkg/, which is the wrong direction. Add a one-line comment that errors.Wrap at the application boundary is wiring-level context, not domain logic."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 19,
"severity": "nit",
"message": "Nice to Have: counterfeiter-directive-on-interface Applies-when clause requires *_test.go files that already import mocks — circular for new packages (no tests yet, no directive, first test import then triggers rule retroactively). Apply to all exported interfaces in non-main service packages unconditionally."
},
{
"file": "docs/go-architecture-patterns.md",
"line": 82,
"severity": "nit",
"message": "Nice to Have: new-prefix-constructor-naming Good example returns UserService interface, which depends on constructor-returns-interface (the next rule block). Reader who jumps here without reading ahead gets an arbitrary-looking interface return. Add a cross-reference or parenthetical in the Why section."
}
],
"concerns_addressed": [
"correctness: go-architecture-assistant agent exists at agents/go-architecture-assistant.md — addressed",
"correctness: all 6 rules use judgment enforcement, correctly documented — no action needed, acknowledged",
"docs: all cross-referenced rule IDs verified present in rules/index.json — addressed",
"docs: go-factory/no-impl-in-factory-pkg exists and exclusion scope is accurate — addressed",
"correctness: ast-grep 0.43.0 limitation acknowledged in counterfeiter-directive-on-interface enforcement field — addressed"
]
}Bot verdict was approve but PR stayed CHANGES_REQUESTED due to 2 MAJOR example-polish findings. Both legit; fixing rather than burning an admin-merge cycle. MAJOR 1 — new-prefix-constructor-naming Bad example shrunk to 2 variants after CreateUserService was removed (factory overlap). Restored illustrative breadth: added BuildUserService alongside MakeUserService and UserSvc. MAJOR 2 — no-globals-or-singletons Good example used UserService interface name with a DoWork method — semantic mismatch with the doc's CRUD identity (Create/Get used everywhere else). Renamed DoWork to Create to match.
There was a problem hiding this comment.
Now let me compile the final review with all findings:
{
"verdict": "approve",
"summary": "PR #16 adds 6 new RULE blocks to go-architecture-patterns.md (counterfeiter-directive-on-interface, new-prefix-constructor-naming, constructor-returns-interface, private-struct-matches-interface, no-globals-or-singletons, business-logic-not-in-main) with corresponding entries in rules/index.json. All entries have consistent schema, unique IDs matching anchors exactly, and the agent mapping in CLAUDE.md correctly identifies go-architecture-assistant as the rule-block owner. No code changes — purely documentation and metadata.",
"comments": [],
"concerns_addressed": [
"correctness: rules/index.json has 6 new entries with no duplicate IDs and anchor fields matching rule IDs exactly — verified by inspection",
"correctness: docs/go-architecture-patterns.md has 6 new RULE blocks with unique anchors — verified by inspection",
"correctness: go-architecture-assistant agent exists at agents/go-architecture-assistant.md — verified by file read",
"correctness: All 6 rules use judgment-based enforcement as documented — no ast-grep baseline claims in enforcement fields",
"tests: rules/index.json entries have consistent schema (level, owner, doc_path) matching existing entries — verified by inspection"
]
}…e-on-interface Mechanical YAML count: 18 -> 19. ast-grep 0.43.0 RECIPE FOR 'NODE WITHOUT PRECEDING COMMENT': The PR #16 enforcement field noted that 'negative-precedes relations are awkward in ast-grep 0.43.0'. Verified during smoke that the correct primitive is 'not.follows' (NOT 'not.precedes'): rule: kind: type_declaration not: follows: kind: comment regex: 'counterfeiter:generate' Direction convention: 'follows' in ast-grep means 'this node comes after the matched sibling' — which for a type_declaration whose counterfeiter comment is the previous sibling is the relation we want. Direction-confusion is the trap PR #16 hit; not.follows is the right primitive. Worth propagating to docs/ast-grep-rule-writing-guide.md as another canonical recipe alongside the PR #11 struct-literal recipe. Verified against fixture with 3 interfaces (2 with counterfeiter comments, 1 without): only the missing-comment case fires. Other 2 are correctly exempt. make precommit clean; check-coverage: OK (124 rules, 19 mechanical YAMLs, no drift).
Summary
Bootstraps `docs/go-architecture-patterns.md` — adds 6 `### RULE` blocks for the architecture-specific conventions not covered by other guides. Mirrors PRs #2-5, #8, #10, #14, #15.
`rules/index.json`: 50 → 56 entries (new `go-architecture/*` family).
Rules added
Cross-references (not duplicated)
The guide also covers context discipline and infinite-loop cancellation, but these are already canonicalized in:
No new rule blocks added for those; the architecture doc keeps the cross-cutting prose as background context and the index keeps a single canonical home per rule.
Pre-emptive checks
Test plan