@use-the-fork/spindle 0.0.0-stage → 0.0.1
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,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Debug Artifacts Left in Production Code
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "console.log, dd(), dump(), var_dump — AI's exploratory leftovers ship to production"
|
|
5
|
+
tags: style, debug, ai-fingerprint, cleanup
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Debug Artifacts Left in Production Code
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (console.log, dd(), dump(), var_dump — AI's exploratory leftovers ship to production)**
|
|
11
|
+
|
|
12
|
+
`console.log("here")`, `console.log("got user", user)`, `dd($order)`, `dump($result)`, `var_dump($payload)`, `print_r($data)`, `echo $error` — these are the breadcrumbs left from when the developer (or AI) was debugging. They ship to production and:
|
|
13
|
+
|
|
14
|
+
- Leak sensitive data into stdout / log aggregators (PII, tokens)
|
|
15
|
+
- Bloat production logs to the point you can't grep for real signal
|
|
16
|
+
- `dd()` literally halts execution — if it reaches prod, your endpoint returns "1" + var_dump output instead of JSON
|
|
17
|
+
|
|
18
|
+
AI is particularly bad about this because the model tends to add `console.log("got result", x)` "to help with debugging" and rarely removes it before "finalising" the function.
|
|
19
|
+
|
|
20
|
+
## Incorrect
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// ❌ Debug artifacts left in
|
|
24
|
+
|
|
25
|
+
async function processPayment(order: Order, token: string): Promise<Charge> {
|
|
26
|
+
console.log("processPayment start", order.id); // shipped
|
|
27
|
+
console.log("token", token); // SHIPS THE TOKEN
|
|
28
|
+
const charge = await stripe.charges.create({/* ... */});
|
|
29
|
+
console.log("got charge", charge); // shipped
|
|
30
|
+
return charge;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```php
|
|
35
|
+
// ❌ Same in PHP
|
|
36
|
+
public function processWebhook(Request $request): JsonResponse
|
|
37
|
+
{
|
|
38
|
+
$payload = $request->json()->all();
|
|
39
|
+
dd($payload); // halts execution; returns a debug page
|
|
40
|
+
// …rest never runs
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public function calculateTax(Order $order): Money
|
|
44
|
+
{
|
|
45
|
+
dump($order); // prints to stdout in production
|
|
46
|
+
print_r($order->items);
|
|
47
|
+
$taxRate = 0.06;
|
|
48
|
+
var_dump($taxRate);
|
|
49
|
+
return $order->subtotal->multiplied($taxRate);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Why it's slop:**
|
|
54
|
+
|
|
55
|
+
- `dd()` in a controller is an outage — the request never completes
|
|
56
|
+
- `console.log('token', token)` is a credentials leak; on serverless logs, every Stripe call ships the token to CloudWatch
|
|
57
|
+
- `dump()` / `var_dump()` show up in HTTP responses if not in a CLI context (especially during `artisan tinker` or test failures)
|
|
58
|
+
- A repo with 50+ stray `console.log` in production paths signals nobody is reading their own code before merging
|
|
59
|
+
|
|
60
|
+
## Correct
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// ✅ No debug; if logging matters, use the proper logger with context
|
|
64
|
+
import { logger } from "@/lib/logger";
|
|
65
|
+
|
|
66
|
+
async function processPayment(order: Order, token: string): Promise<Charge> {
|
|
67
|
+
// Real logger — structured, redacts secrets, levels enforced
|
|
68
|
+
const log = logger.child({ orderId: order.id });
|
|
69
|
+
log.info("payment.start");
|
|
70
|
+
|
|
71
|
+
const charge = await stripe.charges.create({/* ... */});
|
|
72
|
+
|
|
73
|
+
log.info("payment.success", { chargeId: charge.id, amountCents: charge.amount });
|
|
74
|
+
return charge;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```php
|
|
79
|
+
// ✅ Structured logging at the boundary; no debug() calls
|
|
80
|
+
|
|
81
|
+
public function processWebhook(Request $request): JsonResponse
|
|
82
|
+
{
|
|
83
|
+
Log::withContext([
|
|
84
|
+
'webhook_id' => $request->header('Stripe-Webhook-Id'),
|
|
85
|
+
'event_type' => $request->json('type'),
|
|
86
|
+
])->info('webhook.received');
|
|
87
|
+
|
|
88
|
+
// … actual handling …
|
|
89
|
+
|
|
90
|
+
return response()->json(['ok' => true]);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Why it reads human:**
|
|
95
|
+
|
|
96
|
+
- A logger with structured fields and levels (info/warn/error) — not stdout spam
|
|
97
|
+
- Tokens / secrets get redacted by the logger (or not logged at all)
|
|
98
|
+
- The log lines are intentional, useful for production debugging, and won't break the response
|
|
99
|
+
|
|
100
|
+
## When debug calls in production code ARE warranted
|
|
101
|
+
|
|
102
|
+
Rare. Usually zero. Specific cases:
|
|
103
|
+
|
|
104
|
+
- **Logs in well-defined CLI scripts** that are _meant_ to be verbose: a one-off data migration script can use `echo` / `console.log` freely
|
|
105
|
+
- **Test-only files** (`*.test.ts`, `*Test.php`) — fine to keep debug there during development
|
|
106
|
+
- **Explicit `if (DEBUG_MODE) console.log(...)`** wrapped behind a feature flag — fine, but rare in practice
|
|
107
|
+
|
|
108
|
+
Production controllers, services, jobs, listeners, middleware: **zero raw debug calls**.
|
|
109
|
+
|
|
110
|
+
## Detection
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# JavaScript / TypeScript — console.log in production code
|
|
114
|
+
grep -rEn '\bconsole\.(log|debug|info|warn)\(' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' \
|
|
115
|
+
src/ resources/js/ 2>/dev/null \
|
|
116
|
+
| grep -v -E '\.test\.|\.spec\.|/__tests__/|/scripts/'
|
|
117
|
+
|
|
118
|
+
# PHP — debug helpers
|
|
119
|
+
grep -rEn '\b(dd|dump|var_dump|print_r|var_export)\s*\(' --include='*.php' \
|
|
120
|
+
app/ 2>/dev/null
|
|
121
|
+
|
|
122
|
+
# CI gate — block PRs that introduce debug artifacts
|
|
123
|
+
NEW_DEBUG=$(git diff --diff-filter=ACM origin/main...HEAD -- 'app/**/*.php' 'src/**/*.ts' \
|
|
124
|
+
| grep -E '^\+.*\b(console\.log|dd\(|dump\(|var_dump\(|print_r\()')
|
|
125
|
+
test -z "$NEW_DEBUG" || { echo "Debug artifacts in PR:"; echo "$NEW_DEBUG"; exit 1; }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Oxlint configuration (`.oxlintrc.json`):
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"rules": {
|
|
133
|
+
"no-console": ["error", { "allow": ["warn", "error"] }]
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Mago's `no-debug-symbols` flags PHP debug calls such as `var_dump`, `print_r`, and `dd`:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
mago lint --only no-debug-symbols
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Reference: [Laravel Logging docs](https://laravel.com/docs/logging) · [Oxlint no-console](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-console.html) · [Mago no-debug-symbols](https://mago.carthage.software/1.59.0/en/tools/linter/rules/#no-debug-symbols) · [Pino structured logging](https://github.com/pinojs/pino)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Hyper-Consistent Formatting
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: "A codebase where every file looks linter-perfect is suspiciously not-human"
|
|
5
|
+
tags: style, formatting, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Hyper-Consistent Formatting
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (A codebase where every file looks linter-perfect is suspiciously not-human)**
|
|
11
|
+
|
|
12
|
+
Real codebases have geology — older files use older conventions, busy modules have rushed sections, 2am hotfix code has different spacing than carefully-reviewed weeks. AI-generated code is **uniformly polished**: every function the same length, every blank line in the same place, every import alphabetized, no styling drift anywhere. This isn't a hallmark of quality — it's a hallmark of generation.
|
|
13
|
+
|
|
14
|
+
The fingerprint is **uniformity at scale**. One file looking pristine is fine. A 2000-line PR where every file is identically pristine, across files that should have different velocity histories, is a strong AI signal.
|
|
15
|
+
|
|
16
|
+
## What it looks like
|
|
17
|
+
|
|
18
|
+
The PR diff shows:
|
|
19
|
+
|
|
20
|
+
- Every PHP file has exactly 4-space indent, exactly one blank line between methods, exactly two blank lines between class members, every parameter aligned identically
|
|
21
|
+
- Every TS file has the same import-grouping pattern, named imports always alphabetised, every arrow function body wrapped identically
|
|
22
|
+
- No `// HACK:` / `// XXX:` / late-night comments anywhere
|
|
23
|
+
- No commented-out code (clean — but also no signs of someone exploring)
|
|
24
|
+
- Identical commit-message structure across 50 commits
|
|
25
|
+
- All files use the same paradigm (e.g., every function is an arrow function; every class uses constructor property promotion) even in places where the existing codebase mixed styles
|
|
26
|
+
|
|
27
|
+
## Why this matters
|
|
28
|
+
|
|
29
|
+
Code geology is a tool for the next developer:
|
|
30
|
+
|
|
31
|
+
- **Different ages tell different stories** — "this section is from 2022, before we switched to..."
|
|
32
|
+
- **`// HACK:` markers signal known fragility** that hasn't been worth refactoring
|
|
33
|
+
- **Commented-out code reveals exploration** — "we tried X, didn't work, kept the option in case"
|
|
34
|
+
- **Velocity differences** show which paths were rushed and might harbour bugs
|
|
35
|
+
|
|
36
|
+
A repo with no geology forces every reader to start fresh — no inherited context, no "the team didn't bother fixing this, it works fine" signal. AI-generated codebases are flat: every line treated as equally important.
|
|
37
|
+
|
|
38
|
+
## The "all green field" tell
|
|
39
|
+
|
|
40
|
+
Hyper-consistency is most suspicious in **brownfield additions** — i.e., when AI adds code to a codebase that has its own conventions. Real engineers either:
|
|
41
|
+
|
|
42
|
+
- **Match the existing style** (consistent with the surrounding 50 files)
|
|
43
|
+
- **Refactor the surrounding files** alongside their change (drift announces itself in the diff)
|
|
44
|
+
- **Introduce a deliberate change** with an explanation ("new files use the new pattern; old files updated as touched")
|
|
45
|
+
|
|
46
|
+
AI typically does the first poorly — it picks "the most recent convention" or "the most popular convention from training data" rather than matching the actual codebase. Result: the new files are linter-perfect by some standard, but inconsistent with the surrounding codebase.
|
|
47
|
+
|
|
48
|
+
## What "human formatting" looks like
|
|
49
|
+
|
|
50
|
+
You won't catch this with a linter. It's a feel:
|
|
51
|
+
|
|
52
|
+
- Some files have slightly off blank-line spacing where someone hit enter twice
|
|
53
|
+
- Imports sometimes group "stuff I added recently" at the top
|
|
54
|
+
- One file has 6-space indent inside a heredoc because the dev didn't fight prettier on it
|
|
55
|
+
- A function has an extra blank line before a tricky if-branch where the author paused to think
|
|
56
|
+
- A 2-line comment is in mid-sentence-case because someone typed it angry
|
|
57
|
+
|
|
58
|
+
These tiny artifacts are how humans write code. Their absence — especially across many files at once — is the slop.
|
|
59
|
+
|
|
60
|
+
## Detection
|
|
61
|
+
|
|
62
|
+
There's no automatic detector. The signal is human-judgment:
|
|
63
|
+
|
|
64
|
+
1. **Run the diff through `git diff --stat`** — many files changed at once is normal; many files all with the same line-count profile is suspicious
|
|
65
|
+
2. **`git log --author` distribution** — if the diff is from one author but covers 30 files in one commit, raise an eyebrow
|
|
66
|
+
3. **Compare new files to surrounding files** — does the indentation, import order, brace style match the existing convention?
|
|
67
|
+
4. **Search for `// HACK:` / `// XXX:` markers** in the new files — their _absence_ is the signal:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git diff origin/main...HEAD -- '*.ts' '*.php' | grep -E '// HACK:|// XXX:|// FIXME' || echo "NO HACK/XXX MARKERS"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
5. **Mixed-paradigm check** — in a TS PR, count `function` declarations vs arrow functions; in a PHP PR, count `final class` vs `class`. A sudden swing is a tell.
|
|
74
|
+
|
|
75
|
+
## What to do
|
|
76
|
+
|
|
77
|
+
If a PR looks hyper-consistent:
|
|
78
|
+
|
|
79
|
+
- Ask the author: "Was this AI-assisted? Walk me through the section in `OrderService` line 80." Their ability to explain the _intent_, not just the code, separates "AI-written, human-reviewed" from "AI-written, accepted-as-is".
|
|
80
|
+
- Sample a few functions and ask: "What's the trade-off you chose here?" If they shrug, the code wasn't really written by them.
|
|
81
|
+
|
|
82
|
+
The goal isn't to ban AI assistance — it's to make sure the author owns the code they ship.
|
|
83
|
+
|
|
84
|
+
Reference: Internal: [`style-no-hack-scars`](style-no-hack-scars.md)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No HACK Scars — Suspiciously Pristine Code
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: "Real codebases have geology; absence of any '// HACK:' / '// XXX:' markers is itself a tell"
|
|
5
|
+
tags: style, geology, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## No HACK Scars — Suspiciously Pristine Code
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (Real codebases have geology; absence of any '// HACK:' / '// XXX:' markers is itself a tell)**
|
|
11
|
+
|
|
12
|
+
A real codebase has _scars_. `// HACK:` comments where someone went around the type system to ship. `// XXX:` markers where the engineer noticed something fishy but had to ship. Files with a "we tried it the right way; this is the workaround until Stripe v2" note. These markers signal:
|
|
13
|
+
|
|
14
|
+
- A human engineered the code, hit a real constraint, and acknowledged the trade-off
|
|
15
|
+
- The team agreed to ship with the trade-off rather than over-engineer
|
|
16
|
+
- A future engineer is being warned
|
|
17
|
+
|
|
18
|
+
AI-generated codebases are flat: no `// HACK:`, no `// XXX:`, no "we know this is wrong but" markers. Every line presented as equally polished. The _absence_ of scars is the slop signal — real systems don't look like this.
|
|
19
|
+
|
|
20
|
+
## The pattern
|
|
21
|
+
|
|
22
|
+
You can audit a repo's "humanity" by looking for these markers:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git grep -E '// (HACK|XXX|NOTE|GOTCHA|HMMMM|WTF)' | wc -l
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A repo with 50 of these is alive. A repo with zero across a 50K-line codebase is either:
|
|
29
|
+
|
|
30
|
+
- A library that was rigorously reviewed across many years (rare)
|
|
31
|
+
- An AI-generated codebase passing as human (more common)
|
|
32
|
+
|
|
33
|
+
You're not looking for high counts — you're looking for **some**. Two or three `// HACK:` markers in 5K LoC is healthy.
|
|
34
|
+
|
|
35
|
+
## What healthy scars look like
|
|
36
|
+
|
|
37
|
+
```php
|
|
38
|
+
// ✅ Real human marker — explains the constraint
|
|
39
|
+
public function chargeCustomer(int $cents, string $token): string
|
|
40
|
+
{
|
|
41
|
+
// HACK: Stripe v1 SDK doesn't expose retry headers; manually parse from Response
|
|
42
|
+
// until we migrate to v2 (tracked in #1842).
|
|
43
|
+
try {
|
|
44
|
+
return $this->stripe->charges->create([...])->id;
|
|
45
|
+
} catch (RateLimitException $e) {
|
|
46
|
+
$retryAfter = (int) ($e->getResponse()->headers['Retry-After'] ?? 30);
|
|
47
|
+
sleep($retryAfter);
|
|
48
|
+
return $this->stripe->charges->create([...])->id;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// ✅ XXX marker that warns the next reader
|
|
55
|
+
function parseEnvNumber(key: string): number {
|
|
56
|
+
const raw = process.env[key];
|
|
57
|
+
// XXX: parseInt('1.5') === 1, parseFloat('1.5e10') === 15000000000 — fine for us
|
|
58
|
+
// because we only use this for ports and integer caps. Audit before reusing.
|
|
59
|
+
return parseInt(raw ?? "0", 10);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```php
|
|
64
|
+
// ✅ NOTE marker capturing tribal knowledge
|
|
65
|
+
class OrderObserver
|
|
66
|
+
{
|
|
67
|
+
public function creating(Order $order): void
|
|
68
|
+
{
|
|
69
|
+
// NOTE: tax_amount must be set BEFORE Stripe charge; the webhook handler
|
|
70
|
+
// re-reads this row and expects it to be non-zero.
|
|
71
|
+
$order->tax_amount = TaxCalculator::for($order);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
These tell the next engineer: _"someone thought about this; the workaround is intentional; if you change it, here's the context."_
|
|
77
|
+
|
|
78
|
+
## What absence looks like
|
|
79
|
+
|
|
80
|
+
Five files, 600 lines, all in a PR:
|
|
81
|
+
|
|
82
|
+
- Every method has the same length and shape
|
|
83
|
+
- Every error path is the same `try { ... } catch (e) { console.error(e); }`
|
|
84
|
+
- No comments about edge cases, browser quirks, library bugs, race conditions
|
|
85
|
+
- No `// HACK:` / `// XXX:` / `// NOTE:` anywhere
|
|
86
|
+
- Every variable has a "perfect" name
|
|
87
|
+
|
|
88
|
+
That's not human. Real production code dealing with real third parties has scars.
|
|
89
|
+
|
|
90
|
+
## What to do when you find pristine-looking code
|
|
91
|
+
|
|
92
|
+
This rule is harder to action than to detect. Recommended response:
|
|
93
|
+
|
|
94
|
+
1. **Pair-review with the author** — ask them to walk you through one of the "perfect" sections. If they can articulate the _why_ (and the trade-offs they considered), it's fine. If they can't, treat it as AI-assisted code that needs deeper review.
|
|
95
|
+
2. **Run mutation testing on the new code** — Stryker (TS) / Infection (PHP). Mirror-tests die fast.
|
|
96
|
+
3. **Sample edge cases** — try inputs the AI wouldn't have thought of (empty string, very long string, Unicode, negative numbers, future dates, leap years). If the code falls over on basics, the polish was cosmetic.
|
|
97
|
+
|
|
98
|
+
## Detection
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Count scars in the existing repo (the baseline)
|
|
102
|
+
git grep -cE '// (HACK|XXX|NOTE|GOTCHA|WTF|FIXME)' -- '*.ts' '*.tsx' '*.php' '*.js' '*.jsx' 2>/dev/null | head
|
|
103
|
+
|
|
104
|
+
# Same, but for the PR diff only — if a 1000-line PR adds zero scars, suspicious
|
|
105
|
+
git diff origin/main...HEAD -- '*.ts' '*.tsx' '*.php' | \
|
|
106
|
+
grep -cE '^\+.*// (HACK|XXX|NOTE|GOTCHA|WTF)'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A PR adding > 500 LoC with zero `HACK:` / `XXX:` / `NOTE:` is unusual. Real engineering on real systems leaves these.
|
|
110
|
+
|
|
111
|
+
**Note:** this rule cuts both ways. Don't _add_ a `// HACK:` just to look human. Add them when you actually have a hack to mark. The signal is genuine, not performative.
|
|
112
|
+
|
|
113
|
+
Reference: Internal: [`style-hyper-consistent`](style-hyper-consistent.md)
|
package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-trivial-boilerplate.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Trivial Boilerplate
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: "Pattern-matched boilerplate that hides intent and inflates line count"
|
|
5
|
+
tags: style, boilerplate, ai-fingerprint, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Trivial Boilerplate
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (Pattern-matched boilerplate that hides intent and inflates line count)**
|
|
11
|
+
|
|
12
|
+
The class of "code that says less than the underlying expression". Common AI variants:
|
|
13
|
+
|
|
14
|
+
- `if (x) return true; else return false;` — when `return x` does it
|
|
15
|
+
- `return x === true ? true : false;` — ditto
|
|
16
|
+
- `const isPaid: boolean = order.status === 'paid';` — TS infers `boolean` already; the annotation is noise
|
|
17
|
+
- `const name: string = 'asyraf';` — TS infers `string`
|
|
18
|
+
- `await Promise.resolve(value)` — when `value` is already non-Promise
|
|
19
|
+
- `try { await fn(); } catch (e) { throw e; }` — pass-through catch
|
|
20
|
+
- `[...array]` to "make a copy" when the next operation doesn't mutate
|
|
21
|
+
|
|
22
|
+
Each is a small AI fingerprint. A few are fine. A repo with many of them across files reads as model-generated.
|
|
23
|
+
|
|
24
|
+
## Incorrect
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// ❌ if-true-false
|
|
28
|
+
function isPaid(order: Order): boolean {
|
|
29
|
+
if (order.status === "paid") {
|
|
30
|
+
return true;
|
|
31
|
+
} else {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ❌ Ternary returning the booleans it was given
|
|
37
|
+
function isCompleted(order: Order): boolean {
|
|
38
|
+
return order.status === "completed" ? true : false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ❌ Redundant type annotations on obvious literals
|
|
42
|
+
const name: string = "asyraf";
|
|
43
|
+
const age: number = 30;
|
|
44
|
+
const isActive: boolean = true;
|
|
45
|
+
const orders: Order[] = await db.orders.find(); // db.orders.find() return type IS Order[]
|
|
46
|
+
|
|
47
|
+
// ❌ Pass-through catch
|
|
48
|
+
async function processPayment(order: Order): Promise<Charge> {
|
|
49
|
+
try {
|
|
50
|
+
return await stripe.charges.create({/* ... */});
|
|
51
|
+
} catch (e) {
|
|
52
|
+
throw e; // catch literally does nothing
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ❌ Unnecessary await + Promise.resolve
|
|
57
|
+
async function formatName(user: User): Promise<string> {
|
|
58
|
+
return await Promise.resolve(`${user.first} ${user.last}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ❌ Spread to "copy" — but nothing mutates
|
|
62
|
+
function totalsFor(items: Item[]): number {
|
|
63
|
+
const copied = [...items]; // unused: the next op doesn't mutate
|
|
64
|
+
return copied.reduce((s, i) => s + i.price, 0);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```php
|
|
69
|
+
// ❌ PHP equivalents
|
|
70
|
+
public function isPaid(Order $order): bool
|
|
71
|
+
{
|
|
72
|
+
if ($order->status === 'paid') {
|
|
73
|
+
return true;
|
|
74
|
+
} else {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public function processWebhook(Request $request): JsonResponse
|
|
80
|
+
{
|
|
81
|
+
try {
|
|
82
|
+
return $this->handle($request);
|
|
83
|
+
} catch (\Exception $e) {
|
|
84
|
+
throw $e; // pass-through
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Why it's slop:**
|
|
90
|
+
|
|
91
|
+
- Each line carries no information beyond the simpler form
|
|
92
|
+
- The `try { ... } catch (e) { throw e; }` is the most embarrassing — six tokens to do nothing
|
|
93
|
+
- Type annotations on inferred literals fight the type system instead of using it
|
|
94
|
+
- A reader's eyes have to traverse "what does this expand to?" rather than read the expression directly
|
|
95
|
+
|
|
96
|
+
## Correct
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// ✅ Direct expressions
|
|
100
|
+
function isPaid(order: Order): boolean {
|
|
101
|
+
return order.status === "paid";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isCompleted(order: Order): boolean {
|
|
105
|
+
return order.status === "completed";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ✅ Let TS infer
|
|
109
|
+
const name = "asyraf";
|
|
110
|
+
const age = 30;
|
|
111
|
+
const isActive = true;
|
|
112
|
+
const orders = await db.orders.find();
|
|
113
|
+
|
|
114
|
+
// ✅ No catch when not handling
|
|
115
|
+
async function processPayment(order: Order): Promise<Charge> {
|
|
116
|
+
return stripe.charges.create({/* ... */});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ✅ No need to await a non-Promise
|
|
120
|
+
function formatName(user: User): string {
|
|
121
|
+
return `${user.first} ${user.last}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ✅ No spread when nothing mutates
|
|
125
|
+
function totalsFor(items: Item[]): number {
|
|
126
|
+
return items.reduce((s, i) => s + i.price, 0);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```php
|
|
131
|
+
// ✅ PHP equivalents
|
|
132
|
+
public function isPaid(Order $order): bool
|
|
133
|
+
{
|
|
134
|
+
return $order->status === 'paid';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public function processWebhook(Request $request): JsonResponse
|
|
138
|
+
{
|
|
139
|
+
return $this->handle($request);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Why it reads human:**
|
|
144
|
+
|
|
145
|
+
- Each expression does exactly the work; nothing extra
|
|
146
|
+
- Type inference does its job; the type annotations are added where they earn their place (function signatures, public APIs)
|
|
147
|
+
- No pass-through catch; if an error needs handling, it's handled
|
|
148
|
+
|
|
149
|
+
## When annotations / spreads ARE worth it
|
|
150
|
+
|
|
151
|
+
A few cases:
|
|
152
|
+
|
|
153
|
+
- **Public-API parameters / returns** — explicit types document the contract: `function add(a: number, b: number): number`
|
|
154
|
+
- **Complex inferred types** — annotate when the inferred type is hard to read
|
|
155
|
+
- **Boundary code** — `as const`, `satisfies`, or explicit annotations on configuration objects help downstream type narrowing
|
|
156
|
+
- **Spread when you actually want a copy** — before sorting, before mutating, before passing to a function that mutates
|
|
157
|
+
|
|
158
|
+
The trivial-boilerplate test: **does removing the construct change the program's Behavior or the reader's understanding?** If no, remove.
|
|
159
|
+
|
|
160
|
+
## Detection
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# if-true-false
|
|
164
|
+
grep -rEnB1 -A2 'if\s*\([^)]+\)\s*\{?\s*$' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.php' \
|
|
165
|
+
src/ app/ 2>/dev/null | grep -B2 -A1 'return true\b' | grep -A2 'else' | head
|
|
166
|
+
|
|
167
|
+
# Pass-through catch
|
|
168
|
+
grep -rEnB0 -A1 'catch\s*\([^)]*\)\s*\{' --include='*.ts' --include='*.tsx' --include='*.php' \
|
|
169
|
+
src/ app/ 2>/dev/null | grep -B1 '^\s*throw\b'
|
|
170
|
+
|
|
171
|
+
# Ternary returning booleans
|
|
172
|
+
grep -rEn '\?\s*true\s*:\s*false\b' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.php' \
|
|
173
|
+
src/ app/
|
|
174
|
+
|
|
175
|
+
# Redundant type annotations on string/number/boolean literals (TS)
|
|
176
|
+
grep -rEn ':\s*(string|number|boolean)\s*=\s*("[^"]*"|[0-9]+|true|false)' --include='*.ts' --include='*.tsx' src/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Oxlint configuration (`.oxlintrc.json`):
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"rules": {
|
|
184
|
+
"no-useless-return": "error",
|
|
185
|
+
"no-useless-catch": "error",
|
|
186
|
+
"no-unneeded-ternary": "error"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Reference: [Oxlint no-useless-catch](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-useless-catch.html) · [Oxlint no-unneeded-ternary](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-unneeded-ternary.html) · [Oxlint no-useless-return](https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-useless-return.html) · [PHP-CS-Fixer rules](https://cs.symfony.com/) · Internal: [`defensive-generic-catch`](defensive-generic-catch.md)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Doesn't Throw" Tests
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "Tests that assert nothing meaningful; pass even when the function does the wrong thing"
|
|
5
|
+
tags: testing, weak-assertions, ai-fingerprint
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## "Doesn't Throw" Tests
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (Tests that assert nothing meaningful; pass even when the function does the wrong thing)**
|
|
11
|
+
|
|
12
|
+
A test that calls the function and then asserts the function didn't throw is barely a test. It verifies one of the weakest possible properties — "the program didn't crash" — and gives false confidence in coverage. AI often defaults to this pattern because it's the easiest way to make a test pass.
|
|
13
|
+
|
|
14
|
+
A real test verifies a specific outcome. "Didn't throw" is an outcome only in the narrowest cases (you specifically want to verify a thrown error from a previous bug is now absent).
|
|
15
|
+
|
|
16
|
+
## Incorrect
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// ❌ Tests that just verify "the call completes"
|
|
20
|
+
|
|
21
|
+
describe("OrderService", () => {
|
|
22
|
+
it("places an order", async () => {
|
|
23
|
+
const service = new OrderService();
|
|
24
|
+
await expect(service.place(makeValidOrder())).resolves.not.toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("exports users", async () => {
|
|
28
|
+
const service = new UserExportService();
|
|
29
|
+
const result = await service.export();
|
|
30
|
+
expect(result).toBeDefined(); // weak: undefined is the only failure mode caught
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("handles empty input", () => {
|
|
34
|
+
const result = sum([]);
|
|
35
|
+
expect(result).not.toBeNull(); // passes for 0, NaN, '', false, anything except null/undefined
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```php
|
|
41
|
+
// ❌ Same pattern in PHPUnit / Pest
|
|
42
|
+
test('it places an order', function () {
|
|
43
|
+
$service = new OrderService();
|
|
44
|
+
|
|
45
|
+
expect(fn () => $service->place(makeValidOrder()))->not->toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('export returns something', function () {
|
|
49
|
+
$result = (new UserExportService())->export();
|
|
50
|
+
expect($result)->not->toBeNull();
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Why it's slop:**
|
|
55
|
+
|
|
56
|
+
- "Doesn't throw" tells you nothing about whether the right thing happened
|
|
57
|
+
- `place(order)` could return without throwing AND not actually place the order
|
|
58
|
+
- `export()` could return `undefined` (the test fails) OR `null` (test fails) OR an empty array, a misleading "true", etc. — the test catches only the narrowest failure
|
|
59
|
+
- `not.toBeNull()` passes for `0`, `''`, `NaN`, `false` — all of which are usually bugs
|
|
60
|
+
|
|
61
|
+
## Correct — assert what should happen
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// ✅ Specific outcomes
|
|
65
|
+
|
|
66
|
+
describe("OrderService", () => {
|
|
67
|
+
it("place(order) persists the order and returns the saved row with status=pending", async () => {
|
|
68
|
+
const service = new OrderService(db);
|
|
69
|
+
const order = makeValidOrder({ total: 100_00 });
|
|
70
|
+
|
|
71
|
+
const saved = await service.place(order);
|
|
72
|
+
|
|
73
|
+
expect(saved.id).toBeDefined();
|
|
74
|
+
expect(saved.status).toBe("pending");
|
|
75
|
+
expect(saved.total).toBe(100_00);
|
|
76
|
+
expect(await db.orders.findById(saved.id)).toMatchObject({ status: "pending" });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("exports all active users to CSV", async () => {
|
|
80
|
+
await db.users.insertMany([
|
|
81
|
+
{ email: "a@b.com", active: true },
|
|
82
|
+
{ email: "c@d.com", active: false },
|
|
83
|
+
{ email: "e@f.com", active: true },
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const csv = await new UserExportService(db).export();
|
|
87
|
+
|
|
88
|
+
expect(csv).toContain("a@b.com");
|
|
89
|
+
expect(csv).toContain("e@f.com");
|
|
90
|
+
expect(csv).not.toContain("c@d.com"); // inactive should be excluded
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("sum([]) returns 0", () => {
|
|
94
|
+
expect(sum([])).toBe(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Why it reads human:**
|
|
100
|
+
|
|
101
|
+
- Each test verifies a specific, named outcome
|
|
102
|
+
- Bugs are detected: changing `status: 'pending'` to `'paid'` would fail the test; returning all users (not just active) would fail; `sum([])` returning `NaN` would fail
|
|
103
|
+
- Test names describe the _Behavior_, not the _function called_
|
|
104
|
+
|
|
105
|
+
## When "doesn't throw" IS legitimate
|
|
106
|
+
|
|
107
|
+
Rarely:
|
|
108
|
+
|
|
109
|
+
- **Smoke tests** for a never-throws contract on a public API (one or two tests; not the bulk of the suite)
|
|
110
|
+
- **Regression tests** for a specific previously-thrown error: "after fix #123, calling X with Y no longer throws"
|
|
111
|
+
|
|
112
|
+
In these cases, name the test clearly: `it('does not throw on empty input — regression for #123')`.
|
|
113
|
+
|
|
114
|
+
## Detection
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Tests whose only assertion is "not.toThrow" or "not.toBeNull"
|
|
118
|
+
grep -rEn '\.not\.toThrow\(|\.not\.toBeNull\(|\.toBeDefined\(' \
|
|
119
|
+
--include='*.test.ts' --include='*.spec.ts' --include='*.test.tsx' src/ tests/ \
|
|
120
|
+
| head -30
|
|
121
|
+
|
|
122
|
+
# PHPUnit / Pest equivalents
|
|
123
|
+
grep -rEn '\bexpect\(.*\)->not->toThrow\(\)|->assertNotNull\(\$result\)' \
|
|
124
|
+
--include='*Test.php' --include='*.test.php' tests/
|
|
125
|
+
|
|
126
|
+
# Heuristic: test files where the ratio of weak assertions to strong assertions is high
|
|
127
|
+
# (manual review of files with > 5 weak assertions)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A few weak assertions are fine. A test suite dominated by them is a slop fingerprint.
|
|
131
|
+
|
|
132
|
+
Reference: [Martin Fowler — Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html) · Internal: [`test-mock-everything`](test-mock-everything.md), [`test-mirror-implementation`](test-mirror-implementation.md)
|