@use-the-fork/spindle 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -2
- package/build/Assets/Blueprints/Catalog.js +110 -0
- package/build/Assets/Blueprints/agents/general.agent.md +67 -0
- package/build/Assets/Blueprints/agents/sdd-code-reviewer.agent.md +62 -0
- package/build/Assets/Blueprints/agents/sdd-code-slop-reviewer.agent.md +59 -0
- package/build/Assets/Blueprints/agents/sdd-implementer.agent.md +77 -0
- package/build/Assets/Blueprints/agents/sdd-orchestrator.agent.md +42 -0
- package/build/Assets/Blueprints/agents/sdd-re-review.agent.md +57 -0
- package/build/Assets/Blueprints/agents/sdd-reviewer.agent.md +89 -0
- package/build/Assets/Blueprints/agents/sdd-task-reviewer.agent.md +54 -0
- package/build/Assets/Blueprints/agents/shared-codebase-analyzer.agent.md +151 -0
- package/build/Assets/Blueprints/agents/shared-codebase-locator.agent.md +130 -0
- package/build/Assets/Blueprints/agents/shared-codebase-pattern-finder.agent.md +247 -0
- package/build/Assets/Blueprints/agents/shared-explore.agent.md +53 -0
- package/build/Assets/Blueprints/agents/shared-r.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rw.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rwx.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-web-search-researcher.agent.md +122 -0
- package/build/Assets/Blueprints/instructions/code-commenting.instructions.md +39 -0
- package/build/Assets/Blueprints/instructions/source-architecture.instructions.md +15 -0
- package/build/Assets/Blueprints/skills/asd-ste100/CREDITS.MD +1 -0
- package/build/Assets/Blueprints/skills/asd-ste100/SKILL.md +83 -0
- package/build/Assets/Blueprints/skills/asd-ste100/examples/before-after.md +107 -0
- package/build/Assets/Blueprints/skills/asd-ste100/references/writing-rules.md +65 -0
- package/build/Assets/Blueprints/skills/author-agent/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/author-design/SKILL.md +38 -0
- package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +28 -0
- package/build/Assets/Blueprints/skills/author-skill/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/grill/SKILL.md +33 -0
- package/build/Assets/Blueprints/skills/grill-design/SKILL.md +41 -0
- package/build/Assets/Blueprints/skills/grill-with-docs/SKILL.md +14 -0
- package/build/Assets/Blueprints/skills/implement/SKILL.md +79 -0
- package/build/Assets/Blueprints/skills/implement-spec/SKILL.md +34 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/HTML-REPORT.md +129 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/SKILL.md +74 -0
- package/build/Assets/Blueprints/skills/int-code-review/SKILL.md +76 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/SKILL.md +238 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_sections.md +36 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_template.md +42 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-closing-brace-labels.md +112 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-empty-docblocks.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-narration.md +118 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-placeholder.md +130 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-generic-catch.md +153 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-impossible-null.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-missing-real.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-generic-placeholders.md +120 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-over-descriptive.md +108 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-suffix-abuse.md +167 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-type-in-name.md +100 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-dependency-creep.md +114 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-premature-interface.md +136 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-single-method-class.md +161 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-useless-wrapper.md +140 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-as-any-escape.md +127 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-debug-artifacts.md +144 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-hyper-consistent.md +84 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-no-hack-scars.md +113 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-trivial-boilerplate.md +191 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-doesnt-throw.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mirror-implementation.md +125 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mock-everything.md +148 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-snapshot-abuse.md +131 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/SKILL.md +116 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DEEPENING.md +37 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DESIGN-IT-TWICE.md +44 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/SKILL.md +78 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/ADR-FORMAT.md +47 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/GLOSSARY-FORMAT.md +60 -0
- package/build/Assets/Blueprints/skills/int-prototype/LOGIC.md +67 -0
- package/build/Assets/Blueprints/skills/int-prototype/SKILL.md +30 -0
- package/build/Assets/Blueprints/skills/int-prototype/UI.md +112 -0
- package/build/Assets/Blueprints/skills/int-retrospective/SKILL.md +88 -0
- package/build/Assets/Blueprints/skills/int-retrospective/assets/retrospective-prompt.md +38 -0
- package/build/Assets/Blueprints/skills/int-retrospective/references/assessment.md +56 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +64 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/implementer-prompt.md +40 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/reviewer-prompt.md +43 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/aggregate-review.md +7 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +25 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/implementer-corrections.md +9 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/review-inputs.md +26 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/verification-evidence.md +7 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/SKILL.md +51 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/mocking.md +31 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/tests.md +68 -0
- package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/int-verification-before-completion/SKILL.md +25 -0
- package/build/Assets/Blueprints/skills/int-writing-agents/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/int-writing-for-agents/SKILL.md +86 -0
- package/build/Assets/Blueprints/skills/int-writing-instruction/SKILL.md +57 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +129 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/interactive.md +83 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/reference.md +32 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/arguments.md +50 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/interactive-skills.md +53 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/persuasion-principles.md +220 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/reference-skills.md +20 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +48 -0
- package/build/Assets/Blueprints/skills/retrospective/SKILL.md +16 -0
- package/build/Assets/Blueprints/skills/setup-project/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/to-spec/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/to-tickets/SKILL.md +49 -0
- package/build/Assets/Blueprints/skills/update-tooling/SKILL.md +86 -0
- package/build/Assets/Templates/Catalog.js +12 -0
- package/build/Assets/Templates/Catalog.ts +17 -0
- package/build/Assets/Templates/project/copilot-instructions.md +17 -0
- package/build/Assets/Templates/project/domain.md +51 -0
- package/build/Assets/Templates/project/ticket-tracker.md +33 -0
- package/build/Assets/Templates/sdd/design.md +25 -0
- package/build/Assets/Templates/sdd/spec.md +29 -0
- package/build/Assets/Templates/sdd/ticket.md +15 -0
- package/build/BlueprintManagement/BlueprintManagementServiceProvider.js +15 -0
- package/build/BlueprintManagement/Catalog/BlueprintCatalog.js +238 -0
- package/build/BlueprintManagement/Commands/BlueprintCommandOptions.js +9 -0
- package/build/BlueprintManagement/Commands/ConflictPolicy.js +6 -0
- package/build/BlueprintManagement/Commands/InitCommand.js +117 -0
- package/build/BlueprintManagement/Commands/Prompts.js +66 -0
- package/build/BlueprintManagement/Commands/UpdateCommand.js +86 -0
- package/build/BlueprintManagement/Handlers/Agents/AgentHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/BlueprintHandlers.js +4 -0
- package/build/BlueprintManagement/Handlers/Instructions/InstructionHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/Skills/SkillHandler.js +10 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintCatalogPlanner.js +87 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintRemovalCoordinator.js +78 -0
- package/build/BlueprintManagement/Services/BlueprintReconciliationService.js +391 -0
- package/build/BlueprintManagement/Services/InstallationService.js +54 -0
- package/build/BlueprintManagement/Support/BlueprintFiles.js +113 -0
- package/build/BlueprintManagement/Types/Blueprint.js +1 -0
- package/build/BlueprintManagement/Types/Installation.js +1 -0
- package/build/BlueprintManagement/Types/Reconciliation.js +1 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersion.js +70 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersioning.js +55 -0
- package/build/Commands/Command.js +3 -0
- package/build/Commands/CommandOptions.js +12 -0
- package/build/Config/Config.js +3 -0
- package/build/Foundation/Application.js +112 -0
- package/build/Foundation/ApplicationBuilder.js +23 -0
- package/build/Foundation/Bootstrap/Bootstrapper.js +2 -0
- package/build/Foundation/Bootstrap/DiscoverGitRoot.js +8 -0
- package/build/Foundation/Bootstrap/LoadConsoleArgs.js +47 -0
- package/build/Foundation/Bootstrap/LoadProjectConfiguration.js +144 -0
- package/build/Foundation/Bootstrap/RegisterProviders.js +12 -0
- package/build/Foundation/Bootstrap/RequireProjectConfiguration.js +14 -0
- package/build/Foundation/ConsoleInvocation.js +1 -0
- package/build/Foundation/Container.js +185 -0
- package/build/Foundation/FoundationServiceProvider.js +5 -0
- package/build/Foundation/Kernel.js +30 -0
- package/build/Foundation/Service.js +23 -0
- package/build/Foundation/ServiceProvider.js +12 -0
- package/build/Git/GitServiceProvider.js +8 -0
- package/build/Git/Services/GitService.js +26 -0
- package/build/Project/ProjectServiceProvider.js +8 -0
- package/build/Project/Services/ProjectService.js +124 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRecordCommand.js +35 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRequireCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ClaimCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/CorrectCommand.js +37 -0
- package/build/SubagentDrivenDevelopment/Commands/CreateCommand.js +26 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingCreateCommand.js +39 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingUpdateCommand.js +36 -0
- package/build/SubagentDrivenDevelopment/Commands/FinishCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/ImplementCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/NextCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/ReadyCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/RequirementsDecideCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ResumeCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewCommand.js +55 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewRequireCommand.js +12 -0
- package/build/SubagentDrivenDevelopment/Commands/StatusCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/SubmitCommand.js +59 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCommentCommand.js +24 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCreateCommand.js +25 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketUpdateCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/groups.js +36 -0
- package/build/SubagentDrivenDevelopment/Evidence/GitEvidence.js +449 -0
- package/build/SubagentDrivenDevelopment/Evidence/ReportEvidence.js +80 -0
- package/build/SubagentDrivenDevelopment/Models/Assignments.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Evidence.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Findings.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Options.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Workflow.js +16 -0
- package/build/SubagentDrivenDevelopment/Persistence/AssignmentSchema.js +155 -0
- package/build/SubagentDrivenDevelopment/Persistence/CheckSchema.js +71 -0
- package/build/SubagentDrivenDevelopment/Persistence/FeatureWorkspace.js +76 -0
- package/build/SubagentDrivenDevelopment/Persistence/FileSystem.js +28 -0
- package/build/SubagentDrivenDevelopment/Persistence/FindingSchema.js +81 -0
- package/build/SubagentDrivenDevelopment/Persistence/LedgerRepository.js +322 -0
- package/build/SubagentDrivenDevelopment/Persistence/PathSafety.js +62 -0
- package/build/SubagentDrivenDevelopment/Persistence/ProgressSchema.js +95 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReadinessReader.js +40 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReviewSchema.js +68 -0
- package/build/SubagentDrivenDevelopment/Persistence/TicketRepository.js +147 -0
- package/build/SubagentDrivenDevelopment/Persistence/WorkspaceTransaction.js +282 -0
- package/build/SubagentDrivenDevelopment/Policies/AcceptancePolicy.js +100 -0
- package/build/SubagentDrivenDevelopment/Policies/AssignmentPolicy.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/CheckPolicy.js +93 -0
- package/build/SubagentDrivenDevelopment/Policies/FindingPolicy.js +53 -0
- package/build/SubagentDrivenDevelopment/Policies/InputValidation.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/ReadinessPolicy.js +26 -0
- package/build/SubagentDrivenDevelopment/Policies/RequiredReviewers.js +12 -0
- package/build/SubagentDrivenDevelopment/Policies/ReviewPolicy.js +87 -0
- package/build/SubagentDrivenDevelopment/Policies/WorkflowPolicy.js +85 -0
- package/build/SubagentDrivenDevelopment/Services/AssignmentService.js +269 -0
- package/build/SubagentDrivenDevelopment/Services/ChecksService.js +319 -0
- package/build/SubagentDrivenDevelopment/Services/CorrectionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryCorrectionService.js +253 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryFindingService.js +302 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryService.js +160 -0
- package/build/SubagentDrivenDevelopment/Services/DeliverySubmissionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureCreationService.js +138 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureInspectionService.js +399 -0
- package/build/SubagentDrivenDevelopment/Services/FindingService.js +392 -0
- package/build/SubagentDrivenDevelopment/Services/IntegrationService.js +258 -0
- package/build/SubagentDrivenDevelopment/Services/ReadinessService.js +21 -0
- package/build/SubagentDrivenDevelopment/Services/RequirementsService.js +216 -0
- package/build/SubagentDrivenDevelopment/Services/ReviewService.js +471 -0
- package/build/SubagentDrivenDevelopment/Services/SubmissionService.js +299 -0
- package/build/SubagentDrivenDevelopment/Services/TicketService.js +186 -0
- package/build/SubagentDrivenDevelopment/SubagentDrivenDevelopmentServiceProvider.js +113 -0
- package/build/Support/Records.js +5 -0
- package/build/Support/Repository.js +139 -0
- package/build/Support/Stacks.js +10 -0
- package/build/TemplateRendering/Computed/ComputedContextService.js +13 -0
- package/build/TemplateRendering/Computed/ComputedVariableProvider.js +6 -0
- package/build/TemplateRendering/Computed/ComputedVariableProviders.js +6 -0
- package/build/TemplateRendering/Computed/Providers/InternalSkillDescriptionProvider.js +6 -0
- package/build/TemplateRendering/Computed/Providers/SpindlePackageManagerProvider.js +11 -0
- package/build/TemplateRendering/Computed/ResolveRenderContext.js +18 -0
- package/build/TemplateRendering/Rendering/RenderText.js +43 -0
- package/build/TemplateRendering/TemplateRenderingServiceProvider.js +8 -0
- package/build/Tooling/Commands/ToolingAddCommand.js +29 -0
- package/build/Tooling/Commands/ToolingListCommand.js +19 -0
- package/build/Tooling/Commands/ToolingRemoveCommand.js +19 -0
- package/build/Tooling/Commands/ToolingUpdateCommand.js +30 -0
- package/build/Tooling/Commands/groups.js +4 -0
- package/build/Tooling/Models/ToolingEntry.js +1 -0
- package/build/Tooling/Services/ToolingService.js +278 -0
- package/build/Tooling/ToolingServiceProvider.js +15 -0
- package/build/cli.js +36 -0
- package/build/index.js +31 -0
- package/package.json +54 -3
package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-premature-interface.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Premature Interface (One Implementation, No Plan for a Second)
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "Adds indirection nobody asked for; doubles the surface for every change"
|
|
5
|
+
tags: over-engineering, interfaces, abstractions, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Premature Interface (One Implementation, No Plan for a Second)
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Adds indirection nobody asked for; doubles the surface for every change)**
|
|
11
|
+
|
|
12
|
+
AI defaults to "enterprise-grade" because its training distribution is enterprise-grade. The result: every service gets an interface and one implementation, with no second implementation in the foreseeable future. The interface is pure ceremony — every change updates two files, every test mocks the interface, every reader chases two clicks to find the actual code.
|
|
13
|
+
|
|
14
|
+
**Interfaces earn their place when:**
|
|
15
|
+
|
|
16
|
+
- There IS a second implementation (e.g., `StripeGateway` and `AdyenGateway`)
|
|
17
|
+
- There IS a planned second implementation (in this quarter's roadmap)
|
|
18
|
+
- The implementation is being swapped in tests with a fake (and even then, dependency injection of the concrete type is often fine)
|
|
19
|
+
|
|
20
|
+
**Otherwise: write the concrete type. Add the interface when a second implementation actually shows up.** YAGNI is real.
|
|
21
|
+
|
|
22
|
+
## Incorrect
|
|
23
|
+
|
|
24
|
+
```php
|
|
25
|
+
// ❌ Interface with exactly one implementation, no plan for a second
|
|
26
|
+
|
|
27
|
+
interface UserRepositoryInterface
|
|
28
|
+
{
|
|
29
|
+
public function find(int $id): ?User;
|
|
30
|
+
public function save(User $user): void;
|
|
31
|
+
public function delete(int $id): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
final class UserRepository implements UserRepositoryInterface
|
|
35
|
+
{
|
|
36
|
+
public function find(int $id): ?User { /* ... */ }
|
|
37
|
+
public function save(User $user): void { /* ... */ }
|
|
38
|
+
public function delete(int $id): void { /* ... */ }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Binding in a service provider, mostly to satisfy the interface
|
|
42
|
+
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// ❌ Same TS pattern
|
|
47
|
+
interface IOrderService {
|
|
48
|
+
placeOrder(payload: OrderPayload): Promise<Order>;
|
|
49
|
+
cancelOrder(orderId: string): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class OrderService implements IOrderService {
|
|
53
|
+
async placeOrder(payload: OrderPayload): Promise<Order> {
|
|
54
|
+
/* ... */
|
|
55
|
+
}
|
|
56
|
+
async cancelOrder(orderId: string): Promise<void> {
|
|
57
|
+
/* ... */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Why it's slop:**
|
|
63
|
+
|
|
64
|
+
- Every method declared in two files
|
|
65
|
+
- `find` / `save` / `delete` change — both interface AND implementation must be updated; nothing prevents drift
|
|
66
|
+
- Reader hits `UserRepositoryInterface` in a controller, has to navigate through to find the actual code
|
|
67
|
+
- The `I*` prefix or `*Interface` suffix is itself a tell — clean naming uses domain words without the marker
|
|
68
|
+
|
|
69
|
+
## Correct
|
|
70
|
+
|
|
71
|
+
```php
|
|
72
|
+
// ✅ Concrete class until you need a second implementation
|
|
73
|
+
|
|
74
|
+
final class UserRepository
|
|
75
|
+
{
|
|
76
|
+
public function find(int $id): ?User { /* ... */ }
|
|
77
|
+
public function save(User $user): void { /* ... */ }
|
|
78
|
+
public function delete(int $id): void { /* ... */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Controller depends on the concrete class — Laravel auto-resolves it
|
|
82
|
+
public function show(int $id, UserRepository $users): UserResource
|
|
83
|
+
{
|
|
84
|
+
$user = $users->find($id) ?? throw new NotFoundException;
|
|
85
|
+
return new UserResource($user);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// ✅ Concrete class
|
|
91
|
+
class OrderService {
|
|
92
|
+
async placeOrder(payload: OrderPayload): Promise<Order> {
|
|
93
|
+
/* ... */
|
|
94
|
+
}
|
|
95
|
+
async cancelOrder(orderId: string): Promise<void> {
|
|
96
|
+
/* ... */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**When a second implementation arrives** — e.g., you need a `CachedUserRepository` — _then_ extract the interface. By that point you'll know what shape it should have because you have two concrete examples.
|
|
102
|
+
|
|
103
|
+
**Why it reads human:**
|
|
104
|
+
|
|
105
|
+
- Concrete class is reachable in one click
|
|
106
|
+
- No double-maintenance
|
|
107
|
+
- Adding a second implementation is a deliberate refactor, not a default scaffold
|
|
108
|
+
|
|
109
|
+
## Tests: don't create an interface just to mock
|
|
110
|
+
|
|
111
|
+
If you need to swap the dependency in tests, prefer:
|
|
112
|
+
|
|
113
|
+
1. **Inject the concrete class; instantiate with fakes** (e.g., `UserRepository` with an in-memory `Database` connection)
|
|
114
|
+
2. **Use Laravel's container-bound fake** (`$this->mock(UserRepository::class, ...)`) — no interface needed
|
|
115
|
+
3. **Method-level mocks** via PHPUnit / Vitest
|
|
116
|
+
|
|
117
|
+
An interface created _only_ to mock the dependency in tests is also slop. Real interfaces serve production code paths.
|
|
118
|
+
|
|
119
|
+
## Detection
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Interfaces in the codebase
|
|
123
|
+
grep -rn '^interface\s\|^export interface' --include='*.php' --include='*.ts' app/ src/ \
|
|
124
|
+
| wc -l
|
|
125
|
+
|
|
126
|
+
# For each interface, count implementations — flag interfaces with exactly 1
|
|
127
|
+
# (manual review with `phpstorm` or VSCode Go to Implementation)
|
|
128
|
+
|
|
129
|
+
# PHP heuristic: interfaces with 'Interface' suffix or 'I*' prefix
|
|
130
|
+
grep -rEn '^interface\s+(I[A-Z]|[A-Z][a-zA-Z]+Interface)\b' --include='*.php' app/
|
|
131
|
+
|
|
132
|
+
# TS heuristic
|
|
133
|
+
grep -rEn '^export interface I[A-Z]|^interface I[A-Z]' --include='*.ts' --include='*.tsx' src/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Reference: [Martin Fowler — YAGNI](https://martinfowler.com/bliki/Yagni.html) · [Sandi Metz — The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction) · Internal: [`over-eng-useless-wrapper`](over-eng-useless-wrapper.md), [`naming-suffix-abuse`](naming-suffix-abuse.md)
|
package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-single-method-class.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Single-Method Class That Should Be a Function
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "Wraps a free function in class ceremony for no reason"
|
|
5
|
+
tags: over-engineering, classes, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Single-Method Class That Should Be a Function
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Wraps a free function in class ceremony for no reason)**
|
|
11
|
+
|
|
12
|
+
A class with exactly one public method, no state, and no dependencies should usually be a function. AI generates these because its training data is enterprise Java/C# where classes are mandatory for everything. PHP and TypeScript both support free functions / static methods / single-purpose action classes — the wrapping ceremony is pure slop.
|
|
13
|
+
|
|
14
|
+
The exception: **invokable action classes** (Laravel convention) using `__invoke()` are legitimate when they have constructor-injected dependencies and represent a named domain action. The slop variant is the class with no constructor, no state, one static method, and no clear domain identity.
|
|
15
|
+
|
|
16
|
+
## Incorrect
|
|
17
|
+
|
|
18
|
+
```php
|
|
19
|
+
// ❌ Class wrapping a single pure function
|
|
20
|
+
|
|
21
|
+
class StringFormatter
|
|
22
|
+
{
|
|
23
|
+
public static function slugify(string $input): string
|
|
24
|
+
{
|
|
25
|
+
return Str::slug($input);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class EmailValidator
|
|
30
|
+
{
|
|
31
|
+
public function isValid(string $email): bool
|
|
32
|
+
{
|
|
33
|
+
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class TimestampHelper
|
|
38
|
+
{
|
|
39
|
+
public static function toIso(DateTime $dt): string
|
|
40
|
+
{
|
|
41
|
+
return $dt->format(DateTimeInterface::ATOM);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// ❌ Same in TS
|
|
48
|
+
class StringUtil {
|
|
49
|
+
static capitalize(s: string): string {
|
|
50
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class ResponseFormatter {
|
|
55
|
+
static format(data: unknown): ApiResponse {
|
|
56
|
+
return { data, timestamp: Date.now() };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Why it's slop:**
|
|
62
|
+
|
|
63
|
+
- Callers write `StringFormatter::slugify(...)` instead of `slugify(...)`
|
|
64
|
+
- `new EmailValidator()->isValid($email)` is six tokens for what should be one
|
|
65
|
+
- The class adds no encapsulation (no state to encapsulate)
|
|
66
|
+
- Importing/autoloading the class wastes bytes for zero benefit
|
|
67
|
+
- Pattern is recognisably "Java port" — PHP and TS have first-class functions
|
|
68
|
+
|
|
69
|
+
## Correct
|
|
70
|
+
|
|
71
|
+
```php
|
|
72
|
+
// ✅ Free function or method on the existing domain type
|
|
73
|
+
|
|
74
|
+
// helpers.php (or composer autoload "files")
|
|
75
|
+
function slugify(string $input): string
|
|
76
|
+
{
|
|
77
|
+
return Str::slug($input);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isValidEmail(string $email): bool
|
|
81
|
+
{
|
|
82
|
+
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Or extension methods on Carbon
|
|
86
|
+
class CustomCarbon extends Carbon
|
|
87
|
+
{
|
|
88
|
+
public function toIso(): string { return $this->format(DateTimeInterface::ATOM); }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// ✅ Free exported functions
|
|
94
|
+
export function capitalize(s: string): string {
|
|
95
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function formatResponse<T>(data: T): ApiResponse<T> {
|
|
99
|
+
return { data, timestamp: Date.now() };
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Why it reads human:**
|
|
104
|
+
|
|
105
|
+
- Caller writes `slugify(input)` — one token, no ceremony
|
|
106
|
+
- No autoloader hit, no class instantiation, no dependency to mock in tests
|
|
107
|
+
- Functions are testable directly with no setup
|
|
108
|
+
|
|
109
|
+
## Single-method class is OK when…
|
|
110
|
+
|
|
111
|
+
These are legitimate, NOT slop:
|
|
112
|
+
|
|
113
|
+
```php
|
|
114
|
+
// ✅ Invokable action with injected dependencies — Laravel idiom
|
|
115
|
+
final class PlaceOrder
|
|
116
|
+
{
|
|
117
|
+
public function __construct(
|
|
118
|
+
private PaymentGateway $payments,
|
|
119
|
+
private InventoryService $inventory,
|
|
120
|
+
) {}
|
|
121
|
+
|
|
122
|
+
public function __invoke(OrderRequest $request): Order { /* ... */ }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ✅ Job / command — meant to be queued
|
|
126
|
+
final class SendWeeklyDigest implements ShouldQueue
|
|
127
|
+
{
|
|
128
|
+
public function handle(MailerService $mailer): void { /* ... */ }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ✅ Form Request — Laravel pattern
|
|
132
|
+
final class StoreUserRequest extends FormRequest { /* ... */ }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The test: **does it have state, dependencies, or a domain identity beyond "I wrap a function"?** If yes, it's a class. If no, it should be a function.
|
|
136
|
+
|
|
137
|
+
## Detection
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# PHP — classes with exactly one public method and no constructor injection
|
|
141
|
+
# (rough heuristic: file has 'class X' + exactly one 'public function')
|
|
142
|
+
for f in $(find app/ -name '*.php'); do
|
|
143
|
+
PUBLIC=$(grep -cE '^\s+public function ' "$f")
|
|
144
|
+
CTOR=$(grep -cE '^\s+public function __construct' "$f")
|
|
145
|
+
PROPS=$(grep -cE '^\s+(private|protected) (readonly )?[a-zA-Z]+ \$' "$f")
|
|
146
|
+
if [ "$PUBLIC" = "1" ] && [ "$CTOR" = "0" ] && [ "$PROPS" = "0" ]; then
|
|
147
|
+
echo "SUSPECT (single-method, no state): $f"
|
|
148
|
+
fi
|
|
149
|
+
done
|
|
150
|
+
|
|
151
|
+
# TS — classes with one method and no constructor or fields
|
|
152
|
+
grep -rln 'class\s\+[A-Z]' --include='*.ts' src/ | while read f; do
|
|
153
|
+
METHODS=$(grep -cE '^\s+(public\s+|private\s+|protected\s+)?[a-zA-Z]+\s*\(' "$f")
|
|
154
|
+
CTOR=$(grep -c 'constructor' "$f")
|
|
155
|
+
if [ "$METHODS" = "1" ] && [ "$CTOR" = "0" ]; then
|
|
156
|
+
echo "SUSPECT: $f"
|
|
157
|
+
fi
|
|
158
|
+
done
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Reference: Internal: [`naming-suffix-abuse`](naming-suffix-abuse.md), [`over-eng-useless-wrapper`](over-eng-useless-wrapper.md)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Useless Wrapper Functions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "Adds indirection layer for no behavioral gain; callers chase one extra hop to find real code"
|
|
5
|
+
tags: over-engineering, wrappers, indirection, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Useless Wrapper Functions
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Adds indirection layer for no behavioral gain; callers chase one extra hop to find real code)**
|
|
11
|
+
|
|
12
|
+
A function that exists only to delegate to another function, with no added validation, transformation, or context. AI generates these because it pattern-matches on "wrap external dependencies for testability" but applies it indiscriminately.
|
|
13
|
+
|
|
14
|
+
A wrapper earns its place when it adds **something** the underlying call doesn't: validation, default arguments, error normalisation, logging, retry logic, type narrowing. A wrapper that just forwards arguments is pure indirection.
|
|
15
|
+
|
|
16
|
+
## Incorrect
|
|
17
|
+
|
|
18
|
+
```php
|
|
19
|
+
// ❌ Wrappers that just delegate
|
|
20
|
+
|
|
21
|
+
class UserService
|
|
22
|
+
{
|
|
23
|
+
public function __construct(private UserRepository $repo) {}
|
|
24
|
+
|
|
25
|
+
public function findUser(int $id): ?User
|
|
26
|
+
{
|
|
27
|
+
return $this->repo->find($id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public function deleteUser(int $id): void
|
|
31
|
+
{
|
|
32
|
+
$this->repo->delete($id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public function createUser(array $data): User
|
|
36
|
+
{
|
|
37
|
+
return $this->repo->create($data);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// ❌ Same TS pattern
|
|
44
|
+
class StripeWrapper {
|
|
45
|
+
private stripe: Stripe;
|
|
46
|
+
constructor(stripe: Stripe) {
|
|
47
|
+
this.stripe = stripe;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
charge(amount: number, token: string) {
|
|
51
|
+
return this.stripe.charges.create({ amount, source: token });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
refund(chargeId: string) {
|
|
55
|
+
return this.stripe.refunds.create({ charge: chargeId });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Why it's slop:**
|
|
61
|
+
|
|
62
|
+
- `UserService::findUser($id)` does exactly what `$repo->find($id)` does
|
|
63
|
+
- Every caller now goes through TWO layers (`Service → Repo`) to find the actual logic
|
|
64
|
+
- Adding a parameter requires touching both files
|
|
65
|
+
- Tests of `UserService` mostly verify "did we call the repo correctly?" — they don't verify business Behavior
|
|
66
|
+
|
|
67
|
+
## Correct
|
|
68
|
+
|
|
69
|
+
```php
|
|
70
|
+
// ✅ Drop the wrapper; use the underlying type directly
|
|
71
|
+
|
|
72
|
+
// Controller depends on the Repository or Model directly
|
|
73
|
+
public function show(int $id, UserRepository $users): UserResource
|
|
74
|
+
{
|
|
75
|
+
return new UserResource($users->findOrFail($id));
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## When a wrapper IS worth keeping
|
|
80
|
+
|
|
81
|
+
A wrapper earns its place when it adds something real:
|
|
82
|
+
|
|
83
|
+
```php
|
|
84
|
+
// ✅ Wrapper that normalises errors + adds retry — real value
|
|
85
|
+
final class ResilientStripeGateway
|
|
86
|
+
{
|
|
87
|
+
public function __construct(private Stripe $stripe) {}
|
|
88
|
+
|
|
89
|
+
public function charge(Money $amount, string $token): Charge
|
|
90
|
+
{
|
|
91
|
+
return retry(times: 3, sleepMs: 200, callback: function () use ($amount, $token) {
|
|
92
|
+
try {
|
|
93
|
+
return $this->stripe->charges->create([
|
|
94
|
+
'amount' => $amount->cents(),
|
|
95
|
+
'currency' => $amount->currency()->code(),
|
|
96
|
+
'source' => $token,
|
|
97
|
+
]);
|
|
98
|
+
} catch (CardException $e) {
|
|
99
|
+
throw new PaymentDeclined($e->getMessage(), $e); // domain exception
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This wrapper adds:
|
|
107
|
+
|
|
108
|
+
- Retry on transient failures
|
|
109
|
+
- Domain-specific exception (`PaymentDeclined`, not `CardException`)
|
|
110
|
+
- Type-safe `Money` input rather than raw cents + currency strings
|
|
111
|
+
|
|
112
|
+
That's worth the file.
|
|
113
|
+
|
|
114
|
+
**Why it reads human:**
|
|
115
|
+
|
|
116
|
+
- Wrappers exist when they add value; non-wrappers don't pad the layer count
|
|
117
|
+
- Caller can see "this charge call is resilient" because the type is `ResilientStripeGateway`
|
|
118
|
+
- No mystery hops to chase
|
|
119
|
+
|
|
120
|
+
## Detection
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Heuristic: methods that are one-liners and just call $this->dependency->method($args)
|
|
124
|
+
# PHP — find public methods whose body is exactly one delegate call
|
|
125
|
+
grep -rEn -A1 '^\s+public function [a-zA-Z]+\([^)]*\)' --include='*.php' app/ | \
|
|
126
|
+
awk '/public function/{name=$0; next} /^\s+return \$this->[a-zA-Z]+->[a-zA-Z]+\(/{print name; print $0; print "---"}'
|
|
127
|
+
|
|
128
|
+
# TS — same heuristic
|
|
129
|
+
grep -rEn -A1 '^\s+(public |async )?[a-zA-Z]+\([^)]*\):' --include='*.ts' src/ | \
|
|
130
|
+
awk '/[a-zA-Z]+\(/{name=$0; next} /return this\.[a-zA-Z]+\./{print name; print $0; print "---"}'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The cleanest signal: **call-sites**. If a wrapper is called from exactly one place, it's almost certainly slop. Inline it.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# For each public method in Service.php, count call sites
|
|
137
|
+
# (rough — use phpstorm 'Find Usages' for accurate results)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Reference: [Sandi Metz — The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction) · [Martin Fowler — Inline Function](https://refactoring.com/catalog/inlineFunction.html) · Internal: [`over-eng-single-method-class`](over-eng-single-method-class.md), [`over-eng-premature-interface`](over-eng-premature-interface.md)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: as any / @ts-ignore Escape Hatches
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "AI sprinkles these wherever inference is hard — defeats the type system in exactly the places it earns its value"
|
|
5
|
+
tags: style, typescript, type-safety, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## as any / @ts-ignore Escape Hatches
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (AI sprinkles these wherever inference is hard — defeats the type system in exactly the places it earns its value)**
|
|
11
|
+
|
|
12
|
+
AI's common move when TypeScript types don't line up cleanly: cast to `any`, add `@ts-ignore`, or annotate with `: any`. The escape hatch silences the compiler — and silences the protection it was about to provide. Worst case: a real bug (wrong shape, missing field, wrong arg order) survives review because the compiler stopped warning.
|
|
13
|
+
|
|
14
|
+
A reviewer should treat every `as any` / `@ts-ignore` / `@ts-expect-error` as **a request for justification**. Sometimes legitimate (third-party library with bad types, complex generics, runtime-validated unknowns). Often not.
|
|
15
|
+
|
|
16
|
+
## Incorrect
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// ❌ as any sprinkled to silence the compiler
|
|
20
|
+
|
|
21
|
+
async function processWebhook(payload: unknown): Promise<void> {
|
|
22
|
+
const event = (payload as any).event; // unknown → any without validation
|
|
23
|
+
const orderId = (payload as any).data.order.id; // chain of unsafe access
|
|
24
|
+
await db.orders.update({ where: { id: orderId }, data: { status: "paid" } });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ❌ @ts-ignore over a real type mismatch
|
|
28
|
+
function calculateTotal(items: Item[]): number {
|
|
29
|
+
// @ts-ignore — items.map sometimes returns undefined? not sure
|
|
30
|
+
return items.map((i) => i.price * i.quantity).reduce((a, b) => a + b, 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ❌ : any in function signature
|
|
34
|
+
function processData(data: any) {
|
|
35
|
+
// gives up on the input shape
|
|
36
|
+
return data.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ❌ Mixing ts-ignore with no comment
|
|
40
|
+
function send(): void {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
emailService.send(undefined); // why is undefined OK here?
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Why it's slop:**
|
|
47
|
+
|
|
48
|
+
- `as any` propagates — every subsequent access is also unchecked
|
|
49
|
+
- A real bug (e.g., the payload's actual shape is `payload.event.data.order_id`, not `payload.data.order.id`) doesn't fail until production
|
|
50
|
+
- `@ts-ignore` with no explanation is one of the most-cited AI tells in 2025 reviews
|
|
51
|
+
- `: any` parameters defeat every downstream type check
|
|
52
|
+
|
|
53
|
+
## Correct
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ✅ Validate at the boundary; types flow through downstream
|
|
57
|
+
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
|
|
60
|
+
const WebhookPayload = z.object({
|
|
61
|
+
event: z.string(),
|
|
62
|
+
data: z.object({
|
|
63
|
+
order: z.object({ id: z.string() }),
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
async function processWebhook(payload: unknown): Promise<void> {
|
|
68
|
+
const { event, data } = WebhookPayload.parse(payload); // typed from here on
|
|
69
|
+
await db.orders.update({ where: { id: data.order.id }, data: { status: "paid" } });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ✅ Fix the real type issue rather than ignore it
|
|
73
|
+
function calculateTotal(items: Item[]): number {
|
|
74
|
+
return items.reduce((sum, i) => sum + i.price * i.quantity, 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ✅ Type the input
|
|
78
|
+
function processData<T extends { value: string }>(data: T): string {
|
|
79
|
+
return data.value;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## When the escape hatch IS warranted
|
|
84
|
+
|
|
85
|
+
Legitimate use is narrow:
|
|
86
|
+
|
|
87
|
+
- **Bad third-party types** — the lib's `.d.ts` is wrong; you've filed an upstream issue. Use `@ts-expect-error` (not `@ts-ignore`) with a comment naming the issue:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// @ts-expect-error: upstream types incorrectly require `id` — see https://github.com/lib/issues/420
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- **Migration phase** — moving a JS file to TS gradually; mark debt and track removal
|
|
94
|
+
- **Complex generic constraints** where the compiler can't prove correctness but humans can — annotate with a comment explaining why it's safe
|
|
95
|
+
|
|
96
|
+
Every escape hatch in production code should have an explanatory comment. No bare `// @ts-ignore`.
|
|
97
|
+
|
|
98
|
+
## Detection
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Count escape hatches in the diff / repo
|
|
102
|
+
grep -rEn '\bas any\b' --include='*.ts' --include='*.tsx' src/ | wc -l
|
|
103
|
+
grep -rEn '@ts-ignore|@ts-nocheck' --include='*.ts' --include='*.tsx' src/ | wc -l
|
|
104
|
+
grep -rEn ': any\b' --include='*.ts' --include='*.tsx' src/ | wc -l
|
|
105
|
+
|
|
106
|
+
# Bare @ts-ignore / @ts-expect-error without an explanatory comment
|
|
107
|
+
grep -rEnA1 '@ts-(ignore|expect-error)\s*$' --include='*.ts' --include='*.tsx' src/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Add to the Oxlint configuration (`.oxlintrc.json`):
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"plugins": ["typescript"],
|
|
115
|
+
"rules": {
|
|
116
|
+
"typescript/no-explicit-any": "error",
|
|
117
|
+
"typescript/ban-ts-comment": [
|
|
118
|
+
"error",
|
|
119
|
+
{ "ts-ignore": "allow-with-description", "ts-expect-error": "allow-with-description" }
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Bar:** zero `as any` / bare `@ts-ignore` in new code. Existing ones grandfathered but tracked.
|
|
126
|
+
|
|
127
|
+
Reference: [TypeScript handbook — strict mode](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html) · [Oxlint typescript/no-explicit-any](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-explicit-any.html) · [Oxlint typescript/ban-ts-comment](https://oxc.rs/docs/guide/usage/linter/rules/typescript/ban-ts-comment.html) · [Zod](https://zod.dev/) · Internal: [`defensive-impossible-null`](defensive-impossible-null.md)
|