@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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Placeholder Comments Left In
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: "Reveals AI-generated code that was never completed; ships unfinished work as 'done'"
|
|
5
|
+
tags: comments, placeholder, todo, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Placeholder Comments Left In
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (Reveals AI-generated code that was never completed; ships unfinished work as 'done')**
|
|
11
|
+
|
|
12
|
+
Common AI-generated placeholders that should never reach `main`:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
// implementation
|
|
16
|
+
// implementation here
|
|
17
|
+
// your code here
|
|
18
|
+
// helper function
|
|
19
|
+
// TODO: implement
|
|
20
|
+
// TODO: implement this
|
|
21
|
+
// TODO: add error handling
|
|
22
|
+
// TODO: implement validation
|
|
23
|
+
// FIXME: figure out edge case
|
|
24
|
+
// HACK: temporary
|
|
25
|
+
// placeholder
|
|
26
|
+
// add your logic here
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
These mean the AI produced scaffolding the developer was supposed to fill in. If they reach the PR, they signal the author shipped without reading.
|
|
30
|
+
|
|
31
|
+
Distinct from properly-tagged debt markers — a `// TODO(#1234, asyraf): rewrite after Stripe v2 migration` with an owner, ticket, and concrete unblocking condition is _not_ slop. That's tracked debt.
|
|
32
|
+
|
|
33
|
+
## Incorrect
|
|
34
|
+
|
|
35
|
+
```php
|
|
36
|
+
// ❌ Placeholder comments shipped as 'done'
|
|
37
|
+
|
|
38
|
+
public function calculateRefund(Order $order): Money
|
|
39
|
+
{
|
|
40
|
+
// TODO: implement
|
|
41
|
+
return Money::zero();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public function process(Order $order): void
|
|
45
|
+
{
|
|
46
|
+
// Validate the order
|
|
47
|
+
// TODO: add validation
|
|
48
|
+
|
|
49
|
+
$this->charge($order);
|
|
50
|
+
|
|
51
|
+
// helper function
|
|
52
|
+
// TODO: implement error handling
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ❌ Same in TS
|
|
58
|
+
function exportUsers(): User[] {
|
|
59
|
+
// your code here
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function handlePayment(token: string) {
|
|
64
|
+
// implementation
|
|
65
|
+
// TODO: implement
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Why it's slop:**
|
|
70
|
+
|
|
71
|
+
- The function name promises a Behavior the body doesn't deliver
|
|
72
|
+
- Reviewers usually skip past `// TODO` without realising the function is empty
|
|
73
|
+
- These slip through tests because the placeholder body type-checks
|
|
74
|
+
- If genuinely intended as future work, it belongs in the issue tracker, not the source file
|
|
75
|
+
|
|
76
|
+
## Correct
|
|
77
|
+
|
|
78
|
+
```php
|
|
79
|
+
// ✅ Either implement, or fail loud, or remove the method entirely
|
|
80
|
+
|
|
81
|
+
public function calculateRefund(Order $order): Money
|
|
82
|
+
{
|
|
83
|
+
throw new BadMethodCallException(
|
|
84
|
+
'Refund calculation not yet implemented — see #1842 for the policy spec.'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ✅ Or genuine, tracked work-in-progress with owner + ticket:
|
|
89
|
+
// TODO(#1842, asyraf): cap refunds at original-charge-minus-shipping
|
|
90
|
+
public function calculateRefund(Order $order): Money
|
|
91
|
+
{
|
|
92
|
+
return $order->total->subtract($order->shippingCost);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// ✅ Fail loud so the gap is impossible to ship past
|
|
98
|
+
function exportUsers(): User[] {
|
|
99
|
+
throw new Error("exportUsers: not implemented (see #420)");
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Why it reads human:**
|
|
104
|
+
|
|
105
|
+
- An empty function body that throws is impossible to merge by accident; tests will catch it
|
|
106
|
+
- A tracked `TODO` with `(#issue, @owner)` is real debt, not abandoned scaffolding
|
|
107
|
+
- A human author either implements the function or explicitly fails — they don't ship "Returns nothing for now"
|
|
108
|
+
|
|
109
|
+
## Detection
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Common AI placeholders — match only when prefixed by a comment marker
|
|
113
|
+
# (without the required prefix, the regex would flood on class names like "ServiceImplementation").
|
|
114
|
+
grep -rEn --include='*.php' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' \
|
|
115
|
+
'(//|/\*|#)\s*(implementation|implementation here|your code here|helper function|placeholder|add your logic|add error handling|add validation)\b' \
|
|
116
|
+
app/ src/ 2>/dev/null
|
|
117
|
+
|
|
118
|
+
# Bare TODO/FIXME (without ticket reference)
|
|
119
|
+
grep -rEn --include='*.php' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' \
|
|
120
|
+
'(TODO|FIXME)([^(#]|$)' app/ src/ 2>/dev/null | grep -v '#[0-9]'
|
|
121
|
+
|
|
122
|
+
# Combined: TODOs lacking issue references — CI gate
|
|
123
|
+
NEW=$(git diff --name-only --diff-filter=ACM origin/main...HEAD)
|
|
124
|
+
BARE=$(echo "$NEW" | xargs grep -EnH '\b(TODO|FIXME)\b' 2>/dev/null | grep -v '#[0-9]')
|
|
125
|
+
test -z "$BARE" || { echo "Bare TODO/FIXME found — add (#issue, @owner):"; echo "$BARE"; exit 1; }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
See also `technical-debt`'s `process-todo-fixme-aging` rule — the broader policy for tracked debt markers.
|
|
129
|
+
|
|
130
|
+
Reference: Internal: [`comments-narration`](comments-narration.md)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Generic catch Blocks That Don't Distinguish Errors
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: "82% of AI PRs (OX Security) — masks bugs as 'handled', breaks observability, kills debuggability"
|
|
5
|
+
tags: defensive-programming, error-handling, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Generic catch Blocks That Don't Distinguish Errors
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (82% of AI PRs per OX Security study; masks bugs as 'handled', breaks observability, kills debuggability)**
|
|
11
|
+
|
|
12
|
+
The single most-cited AI anti-pattern in 2025 research. Try/catch wrapped around code, with a generic catch that logs and swallows. Looks responsible. Is the opposite — it hides bugs and converts loud failures into silent corruption.
|
|
13
|
+
|
|
14
|
+
Real error handling distinguishes:
|
|
15
|
+
|
|
16
|
+
- **What can throw** (specific exception types)
|
|
17
|
+
- **What's recoverable** (catch, retry, fall back)
|
|
18
|
+
- **What's a bug** (re-throw, let it propagate)
|
|
19
|
+
|
|
20
|
+
A bare `catch (e) { console.error(e); }` does none of this. It says "if anything goes wrong, I'll keep going" — and "anything" includes programmer errors that should crash so they're noticed.
|
|
21
|
+
|
|
22
|
+
## Incorrect
|
|
23
|
+
|
|
24
|
+
```php
|
|
25
|
+
// ❌ Generic catch that swallows everything
|
|
26
|
+
|
|
27
|
+
public function processPayment(Order $order): bool
|
|
28
|
+
{
|
|
29
|
+
try {
|
|
30
|
+
$charge = $this->stripe->charges->create([
|
|
31
|
+
'amount' => $order->total->cents(),
|
|
32
|
+
'currency' => 'usd',
|
|
33
|
+
'source' => $order->paymentToken,
|
|
34
|
+
]);
|
|
35
|
+
$order->update(['stripe_charge_id' => $charge->id, 'status' => 'paid']);
|
|
36
|
+
return true;
|
|
37
|
+
} catch (Exception $e) {
|
|
38
|
+
Log::error('Payment failed: ' . $e->getMessage());
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// ❌ Same TS pattern
|
|
46
|
+
async function exportUsers(): Promise<User[]> {
|
|
47
|
+
try {
|
|
48
|
+
const users = await api.fetchAllUsers();
|
|
49
|
+
return users;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error("Export failed:", e);
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Why it's slop:**
|
|
58
|
+
|
|
59
|
+
- `Exception $e` catches _everything_, including `TypeError` (programmer bug) and `OutOfMemoryError` (system crash) — those should not be "handled" by logging and continuing
|
|
60
|
+
- Returning `false` / `[]` lets the caller think the operation succeeded with no data, masking a real failure
|
|
61
|
+
- `Log::error` lacks context — what was the order? what user? what amount? Future debugger has no anchor
|
|
62
|
+
- A real Stripe error (card declined) gets the same treatment as a typo in the code
|
|
63
|
+
|
|
64
|
+
## Correct
|
|
65
|
+
|
|
66
|
+
```php
|
|
67
|
+
// ✅ Catch specifically; re-throw what you don't understand
|
|
68
|
+
|
|
69
|
+
public function processPayment(Order $order): void
|
|
70
|
+
{
|
|
71
|
+
try {
|
|
72
|
+
$charge = $this->stripe->charges->create([
|
|
73
|
+
'amount' => $order->total->cents(),
|
|
74
|
+
'currency' => 'usd',
|
|
75
|
+
'source' => $order->paymentToken,
|
|
76
|
+
]);
|
|
77
|
+
$order->update(['stripe_charge_id' => $charge->id, 'status' => 'paid']);
|
|
78
|
+
} catch (CardException $e) {
|
|
79
|
+
// Customer-facing failure — known, expected
|
|
80
|
+
$order->update(['status' => 'declined', 'decline_reason' => $e->getStripeCode()]);
|
|
81
|
+
throw new PaymentDeclined($e->getStripeCode(), previous: $e);
|
|
82
|
+
} catch (RateLimitException $e) {
|
|
83
|
+
// Transient — caller retries
|
|
84
|
+
throw new TransientPaymentFailure(retryAfterSeconds: 30, previous: $e);
|
|
85
|
+
}
|
|
86
|
+
// Any other exception propagates: TypeError, OutOfMemoryError, etc.
|
|
87
|
+
// — those are bugs we WANT to know about.
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// ✅ Specific errors, propagate the unknown
|
|
93
|
+
async function exportUsers(): Promise<User[]> {
|
|
94
|
+
try {
|
|
95
|
+
return await api.fetchAllUsers();
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e instanceof AbortError) {
|
|
98
|
+
// Caller aborted; that's a feature, not a failure
|
|
99
|
+
throw e;
|
|
100
|
+
}
|
|
101
|
+
if (e instanceof RateLimitError) {
|
|
102
|
+
await sleep(e.retryAfterMs);
|
|
103
|
+
return api.fetchAllUsers(); // one retry
|
|
104
|
+
}
|
|
105
|
+
// Network / parse / bug — propagate; don't return [] (the caller would think it succeeded with no users)
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Why it reads human:**
|
|
112
|
+
|
|
113
|
+
- Each `catch` branch handles a specific, named condition with a specific remediation
|
|
114
|
+
- Unknown errors propagate — the system fails loud, observability catches them, on-call wakes up before customers do
|
|
115
|
+
- No silent "return false" / "return empty array" — caller can't accidentally treat failure as empty success
|
|
116
|
+
|
|
117
|
+
## The "what would I want at 2am?" test
|
|
118
|
+
|
|
119
|
+
When debugging a production incident, you want:
|
|
120
|
+
|
|
121
|
+
- **Errors at the right loudness** — bugs crash with stack traces; expected failures have domain-specific exceptions you can grep
|
|
122
|
+
- **Context, not just the message** — order ID, user ID, request ID, all in the log line
|
|
123
|
+
- **No silent corruption** — a "successful" call that returned no data is the worst kind of failure
|
|
124
|
+
|
|
125
|
+
The generic-catch pattern fails all three.
|
|
126
|
+
|
|
127
|
+
## Detection
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Bare catches in PHP
|
|
131
|
+
grep -rEn 'catch\s*\(\s*\\?Exception\b' --include='*.php' app/
|
|
132
|
+
|
|
133
|
+
# Bare catches in TS / JS
|
|
134
|
+
grep -rEn '\}\s*catch\s*\(\s*[a-zA-Z_]+\s*\)\s*\{' --include='*.ts' --include='*.tsx' --include='*.js' src/
|
|
135
|
+
|
|
136
|
+
# Catch blocks that only log and continue
|
|
137
|
+
grep -rEnB1 'console\.error\(.*\)\s*;\s*$' --include='*.ts' --include='*.tsx' src/
|
|
138
|
+
|
|
139
|
+
# PHP equivalent
|
|
140
|
+
grep -rEnB1 'Log::(error|warning)\(.*\)' --include='*.php' app/Http/ app/Services/
|
|
141
|
+
|
|
142
|
+
# Linter rules
|
|
143
|
+
# Oxlint: enable no-empty for empty blocks and typescript/no-explicit-any for explicit any
|
|
144
|
+
# These checks do not detect catch blocks that only log and continue.
|
|
145
|
+
# Mago: detect empty PHP catch bodies
|
|
146
|
+
mago lint --only no-empty-catch-clause
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Mago's `no-empty-catch-clause` does not flag non-empty log-and-continue handlers. Review those manually. For catches that cannot match a known exception, the analyzer offers `check-dead-catch`, which requires `check-throws`; both default to `false`. These options check exception flow, not whether a catch is too broad, and undocumented throws can limit their accuracy.
|
|
150
|
+
|
|
151
|
+
A small handful of generic catches is fine (e.g., at HTTP boundaries with structured logging). **Density** is the signal — > 5 generic catches in a single service is the AI fingerprint.
|
|
152
|
+
|
|
153
|
+
Reference: [Oxlint no-empty](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-empty.html) · [Oxlint typescript/no-explicit-any](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-explicit-any.html) · [Mago no-empty-catch-clause](https://mago.carthage.software/1.59.0/en/tools/linter/rules/#no-empty-catch-clause) · [Mago analyzer configuration](https://mago.carthage.software/1.59.0/en/tools/analyzer/configuration-reference/) · Internal: [`defensive-impossible-null`](defensive-impossible-null.md), [`defensive-missing-real`](defensive-missing-real.md)
|
package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-impossible-null.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Null Checks for Impossible Nulls
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "Defensive code in places that can't fail; clutters the path and signals model didn't trust the type system"
|
|
5
|
+
tags: defensive-programming, null-checks, type-safety, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Null Checks for Impossible Nulls
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Defensive code in places that can't fail; clutters the path and signals model didn't trust the type system)**
|
|
11
|
+
|
|
12
|
+
When the type system or surrounding code already guarantees a value is non-null, a defensive null check adds noise and tells the reader "the author wasn't sure". Common cases:
|
|
13
|
+
|
|
14
|
+
- A non-nullable parameter (typed `User $user`, not `?User $user`) — can't be null
|
|
15
|
+
- After `Model::findOrFail()` — never returns null (throws on miss)
|
|
16
|
+
- After a non-null assertion in the previous line
|
|
17
|
+
- Inside a `forEach` / `map` loop where the iterable can't contain null
|
|
18
|
+
|
|
19
|
+
The pattern signals the model defaulted to "check everything for safety" without reading the types around it.
|
|
20
|
+
|
|
21
|
+
## Incorrect
|
|
22
|
+
|
|
23
|
+
```php
|
|
24
|
+
// ❌ Null check after findOrFail — findOrFail throws if not found
|
|
25
|
+
|
|
26
|
+
public function show(int $id): View
|
|
27
|
+
{
|
|
28
|
+
$user = User::findOrFail($id);
|
|
29
|
+
|
|
30
|
+
if ($user === null) {
|
|
31
|
+
abort(404);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return view('users.show', compact('user'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ❌ Null check on a non-nullable parameter
|
|
38
|
+
|
|
39
|
+
public function notify(User $user, string $message): void // $user is required
|
|
40
|
+
{
|
|
41
|
+
if ($user === null) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
Mail::to($user)->send(new GenericNotification($message));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ❌ Null check repeated in same function
|
|
48
|
+
public function process(Order $order): void
|
|
49
|
+
{
|
|
50
|
+
if ($order === null) return;
|
|
51
|
+
$items = $order->items;
|
|
52
|
+
if ($order === null) return; // already checked above; flow can't make it null
|
|
53
|
+
foreach ($items as $item) {
|
|
54
|
+
if ($order === null) return; // still impossible
|
|
55
|
+
// ...
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// ❌ TS: defensive checks the compiler already enforced
|
|
62
|
+
|
|
63
|
+
function greet(user: User): string {
|
|
64
|
+
// `user: User` — non-null in type system
|
|
65
|
+
if (!user) return ""; // dead branch; TS would error
|
|
66
|
+
return `Hi, ${user.name}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ❌ Defensive after a guard
|
|
70
|
+
function process(user: User | null): void {
|
|
71
|
+
if (!user) return; // narrows to User
|
|
72
|
+
if (user) {
|
|
73
|
+
// always true after the guard
|
|
74
|
+
doStuff(user);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ❌ Optional chaining on a definitely-defined value
|
|
79
|
+
const userId = currentUser.id; // currentUser is non-nullable here
|
|
80
|
+
console.log(currentUser?.email); // ?. is unnecessary
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Why it's slop:**
|
|
84
|
+
|
|
85
|
+
- The dead branch is dead — never executes; reviewers must mentally rule it out
|
|
86
|
+
- Signals the author didn't trust the type system
|
|
87
|
+
- `findOrFail` is well-known Laravel API; checking its result for null says "I read the docs but don't believe them"
|
|
88
|
+
- Stacking redundant guards in one function (the third example) is unmistakable
|
|
89
|
+
|
|
90
|
+
## Correct
|
|
91
|
+
|
|
92
|
+
```php
|
|
93
|
+
// ✅ Trust findOrFail — it throws, doesn't return null
|
|
94
|
+
|
|
95
|
+
public function show(int $id): View
|
|
96
|
+
{
|
|
97
|
+
return view('users.show', [
|
|
98
|
+
'user' => User::findOrFail($id),
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ✅ Non-nullable parameter — no check needed
|
|
103
|
+
public function notify(User $user, string $message): void
|
|
104
|
+
{
|
|
105
|
+
Mail::to($user)->send(new GenericNotification($message));
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// ✅ Trust the types
|
|
111
|
+
function greet(user: User): string {
|
|
112
|
+
return `Hi, ${user.name}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ✅ Single guard; TS narrows the type below
|
|
116
|
+
function process(user: User | null): void {
|
|
117
|
+
if (!user) return;
|
|
118
|
+
doStuff(user); // TS knows user is User here
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ✅ No optional chaining when not optional
|
|
122
|
+
console.log(currentUser.email);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Why it reads human:**
|
|
126
|
+
|
|
127
|
+
- Each line carries weight; no dead branches
|
|
128
|
+
- Type system does the work, not runtime checks
|
|
129
|
+
- Reader can scan past the function without ruling out impossible paths
|
|
130
|
+
|
|
131
|
+
## When null checks ARE warranted
|
|
132
|
+
|
|
133
|
+
- **At trust boundaries** — input from HTTP/queue/file, before the type narrows
|
|
134
|
+
- **On nullable returns** from third-party libraries (e.g., `Eloquent::find()` returns `?Model`)
|
|
135
|
+
- **In `try {} catch` flows** where the value might be partially constructed
|
|
136
|
+
- **When the type really IS nullable** — `?User $user` parameters that the caller can leave empty
|
|
137
|
+
|
|
138
|
+
The test: **does the type say it could be null?** If yes, check. If no, trust.
|
|
139
|
+
|
|
140
|
+
## Detection
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Null checks immediately after findOrFail in PHP — a recognisable AI signature
|
|
144
|
+
# (Two-pass: find files with findOrFail, then look for null checks inside them.)
|
|
145
|
+
grep -rl 'findOrFail' --include='*.php' app/ | xargs grep -EnB1 'if\s*\(\s*\$[a-zA-Z_]+\s*===?\s*null\s*\)' 2>/dev/null
|
|
146
|
+
|
|
147
|
+
# TS: optional chaining on values typed as non-null
|
|
148
|
+
# (best caught via TS strict mode: noUncheckedIndexedAccess + strictNullChecks)
|
|
149
|
+
npx tsc --noEmit --strict
|
|
150
|
+
|
|
151
|
+
# PHP: run Mago's type analyzer, not just its syntax-based linter
|
|
152
|
+
mago analyze
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Use Mago's analyzer for PHP type analysis and inspect its diagnostics alongside the guard and surrounding control flow. Detection depends on the available type information, framework stubs, and configured ignores or baseline; a clean run does not prove every null check necessary. For TypeScript, retain strict type checking and manually verify redundant guards.
|
|
156
|
+
|
|
157
|
+
Reference: [Mago analyzer configuration](https://mago.carthage.software/1.59.0/en/tools/analyzer/configuration-reference/) · [Mago analyzer command reference](https://mago.carthage.software/1.59.0/en/tools/analyzer/command-reference/) · [TS strict mode](https://www.typescriptlang.org/tsconfig#strict) · Internal: [`defensive-generic-catch`](defensive-generic-catch.md), [`defensive-missing-real`](defensive-missing-real.md)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Defensive in the Wrong Places — Missing the Real Defenses
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: "Code looks safe but the *actual* failure modes (network, queues, races) are unprotected"
|
|
5
|
+
tags: defensive-programming, timeouts, resilience, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Defensive in the Wrong Places — Missing the Real Defenses
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (Code looks safe but the actual failure modes — network, queues, races — are unprotected)**
|
|
11
|
+
|
|
12
|
+
OX Security 2025: **76% of AI-assisted PRs miss timeouts on external calls**. The same PRs are full of impossible null checks and generic try/catch. The pattern is: defensive in places that don't need it; not defensive in places that do.
|
|
13
|
+
|
|
14
|
+
What AI often misses (the failure modes that actually take down production):
|
|
15
|
+
|
|
16
|
+
- **No timeouts on outbound HTTP** — a slow third-party API hangs your request indefinitely
|
|
17
|
+
- **No retry policy + jitter** — a transient blip cascades into a customer-visible failure
|
|
18
|
+
- **No idempotency keys on payment/order creation** — a network retry creates duplicate charges
|
|
19
|
+
- **No rate-limit checks before hitting an external API** — banned by Stripe / Shopify / etc.
|
|
20
|
+
- **No circuit breaker** — keeps hammering a known-down dependency, queue backs up
|
|
21
|
+
- **No locking / unique constraint** — race condition double-spends inventory
|
|
22
|
+
- **No backpressure on workers** — queue grows unbounded; OOM
|
|
23
|
+
|
|
24
|
+
These are the defenses that matter. A robust system has these; the dramatic-looking try/catch and null checks don't replace them.
|
|
25
|
+
|
|
26
|
+
## Incorrect
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
// ❌ Defensive theatre: try/catch wraps the wrong thing; real risks unprotected
|
|
30
|
+
|
|
31
|
+
public function syncOrder(string $orderId): void
|
|
32
|
+
{
|
|
33
|
+
try {
|
|
34
|
+
// No timeout — could hang for minutes
|
|
35
|
+
$response = Http::get("https://api.shopify.com/orders/{$orderId}");
|
|
36
|
+
$data = $response->json();
|
|
37
|
+
|
|
38
|
+
if ($data === null) { // impossible — Http returns array or throws
|
|
39
|
+
Log::error('Sync failed');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// No idempotency — if this retries, we create duplicates
|
|
44
|
+
Order::create($data);
|
|
45
|
+
} catch (Exception $e) {
|
|
46
|
+
Log::error('Sync failed: ' . $e->getMessage());
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// ❌ Same pattern in TS
|
|
53
|
+
async function syncOrder(orderId: string): Promise<void> {
|
|
54
|
+
try {
|
|
55
|
+
// No timeout; no retry; no backoff
|
|
56
|
+
const res = await fetch(`https://api.shopify.com/orders/${orderId}`);
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
|
|
59
|
+
if (!data) {
|
|
60
|
+
// impossible if res.json() resolved
|
|
61
|
+
console.error("Sync failed");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await db.orders.create({ data });
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.error(e); // swallow
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Why it's slop:**
|
|
73
|
+
|
|
74
|
+
- The `if ($data === null)` is dead defence; the actual failure mode is "Shopify takes 90s to respond and our request timeout hits us first"
|
|
75
|
+
- The try/catch swallows real failures but doesn't add the things that prevent them
|
|
76
|
+
- Retried calls create duplicate orders (idempotency missing)
|
|
77
|
+
- Looks responsible; isn't
|
|
78
|
+
|
|
79
|
+
## Correct
|
|
80
|
+
|
|
81
|
+
```php
|
|
82
|
+
// ✅ The real defences — timeouts, idempotency, retry with backoff, circuit-break
|
|
83
|
+
|
|
84
|
+
public function syncOrder(string $orderId): void
|
|
85
|
+
{
|
|
86
|
+
$response = Http::timeout(10) // hard timeout
|
|
87
|
+
->retry(times: 3, sleepMilliseconds: 200, when: fn ($e) =>
|
|
88
|
+
$e instanceof ConnectionException || $e->getCode() === 429)
|
|
89
|
+
->get("https://api.shopify.com/orders/{$orderId}");
|
|
90
|
+
|
|
91
|
+
$response->throw(); // throw on 4xx/5xx — let it propagate
|
|
92
|
+
|
|
93
|
+
Order::updateOrCreate(
|
|
94
|
+
['shopify_id' => $orderId], // idempotency via unique key
|
|
95
|
+
$response->json()
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// ✅ TS with AbortController for timeout + retry policy
|
|
102
|
+
async function syncOrder(orderId: string): Promise<void> {
|
|
103
|
+
const data = await fetchWithRetry(`https://api.shopify.com/orders/${orderId}`, {
|
|
104
|
+
timeoutMs: 10_000,
|
|
105
|
+
retries: 3,
|
|
106
|
+
retryOnStatus: [429, 502, 503, 504],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Idempotent upsert by external id (unique index on shopify_id)
|
|
110
|
+
await db.orders.upsert({
|
|
111
|
+
where: { shopify_id: orderId },
|
|
112
|
+
create: data,
|
|
113
|
+
update: data,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Why it reads human:**
|
|
119
|
+
|
|
120
|
+
- The actual failure modes (slow third party, transient errors, duplicate retries) are each addressed
|
|
121
|
+
- Errors NOT covered by retry propagate — observability/alerts catch them
|
|
122
|
+
- The idempotency key (`shopify_id`) prevents duplicate orders even under retry storms
|
|
123
|
+
- The "try/catch" is gone — it added no value here
|
|
124
|
+
|
|
125
|
+
## The real-defences checklist
|
|
126
|
+
|
|
127
|
+
For any code that talks to the outside world, ask:
|
|
128
|
+
|
|
129
|
+
- [ ] **Timeout** on every outbound call (HTTP, DB, queue, cache)?
|
|
130
|
+
- [ ] **Retry** policy: how many, with what backoff, on which error types?
|
|
131
|
+
- [ ] **Idempotency**: if the call is retried, will it create duplicate side effects?
|
|
132
|
+
- [ ] **Rate limit** awareness: am I tracking my own request rate or relying on the upstream's error?
|
|
133
|
+
- [ ] **Circuit breaker** for known-down dependencies (or at least a backoff cap)?
|
|
134
|
+
- [ ] **Locking / unique constraints** on writes that could race?
|
|
135
|
+
- [ ] **Backpressure** on workers reading from a queue?
|
|
136
|
+
- [ ] **Real errors propagated** to observability instead of swallowed?
|
|
137
|
+
|
|
138
|
+
A try/catch that doesn't add any of these isn't a defense. It's theatre.
|
|
139
|
+
|
|
140
|
+
## Detection
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# HTTP calls without explicit timeout (Laravel Http facade)
|
|
144
|
+
grep -rEn 'Http::get\(|Http::post\(|Http::put\(|Http::delete\(' --include='*.php' app/ \
|
|
145
|
+
| grep -v 'timeout('
|
|
146
|
+
|
|
147
|
+
# fetch() calls without AbortController / signal
|
|
148
|
+
grep -rEn 'await fetch\(' --include='*.ts' --include='*.tsx' --include='*.js' src/ \
|
|
149
|
+
| grep -vE 'signal:|AbortController'
|
|
150
|
+
|
|
151
|
+
# create() / insert() without idempotency check (rough — manual review)
|
|
152
|
+
grep -rEn '(Order|Charge|Payment)::create\(' --include='*.php' app/
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A repo can pass every other rule in this skill and still ship the wrong defenses. **This rule is the one to take seriously on payment, checkout, and integration code paths.**
|
|
156
|
+
|
|
157
|
+
Reference: [Stripe — Idempotent Requests](https://docs.stripe.com/api/idempotent_requests) · [Hystrix / Resilience4j circuit breaker patterns](https://github.com/resilience4j/resilience4j) · OX Security study · Internal: [`defensive-generic-catch`](defensive-generic-catch.md)
|