@dv.nghiem/flowdeck 0.1.0
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 +136 -0
- package/bin/flowdeck.js +108 -0
- package/dist/agents/architect.d.ts +3 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/code-explorer.d.ts +3 -0
- package/dist/agents/code-explorer.d.ts.map +1 -0
- package/dist/agents/coder.d.ts +3 -0
- package/dist/agents/coder.d.ts.map +1 -0
- package/dist/agents/debug.d.ts +4 -0
- package/dist/agents/debug.d.ts.map +1 -0
- package/dist/agents/doc-updater.d.ts +3 -0
- package/dist/agents/doc-updater.d.ts.map +1 -0
- package/dist/agents/flowdeck.d.ts +5 -0
- package/dist/agents/flowdeck.d.ts.map +1 -0
- package/dist/agents/index.d.ts +38 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/mapper.d.ts +3 -0
- package/dist/agents/mapper.d.ts.map +1 -0
- package/dist/agents/orchestrator.d.ts +10 -0
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/agents/performance.d.ts +4 -0
- package/dist/agents/performance.d.ts.map +1 -0
- package/dist/agents/planner.d.ts +3 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/policy-enforcer.d.ts +3 -0
- package/dist/agents/policy-enforcer.d.ts.map +1 -0
- package/dist/agents/researcher.d.ts +3 -0
- package/dist/agents/researcher.d.ts.map +1 -0
- package/dist/agents/reviewer.d.ts +3 -0
- package/dist/agents/reviewer.d.ts.map +1 -0
- package/dist/agents/risk-analyst.d.ts +3 -0
- package/dist/agents/risk-analyst.d.ts.map +1 -0
- package/dist/agents/security-auditor.d.ts +3 -0
- package/dist/agents/security-auditor.d.ts.map +1 -0
- package/dist/agents/specialist.d.ts +5 -0
- package/dist/agents/specialist.d.ts.map +1 -0
- package/dist/agents/tester.d.ts +3 -0
- package/dist/agents/tester.d.ts.map +1 -0
- package/dist/agents/types.d.ts +20 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/writer.d.ts +3 -0
- package/dist/agents/writer.d.ts.map +1 -0
- package/dist/commands/analysis/analysis.test.d.ts +2 -0
- package/dist/commands/analysis/analysis.test.d.ts.map +1 -0
- package/dist/commands/analysis/analyze-change.d.ts +148 -0
- package/dist/commands/analysis/analyze-change.d.ts.map +1 -0
- package/dist/commands/analysis/evaluate-risk.d.ts +77 -0
- package/dist/commands/analysis/evaluate-risk.d.ts.map +1 -0
- package/dist/commands/analysis/guarded-edit.d.ts +72 -0
- package/dist/commands/analysis/guarded-edit.d.ts.map +1 -0
- package/dist/commands/execution/deploy-check.d.ts +91 -0
- package/dist/commands/execution/deploy-check.d.ts.map +1 -0
- package/dist/commands/execution/fix-bug.d.ts +187 -0
- package/dist/commands/execution/fix-bug.d.ts.map +1 -0
- package/dist/commands/execution/new-feature.d.ts +171 -0
- package/dist/commands/execution/new-feature.d.ts.map +1 -0
- package/dist/commands/execution/review-code.d.ts +130 -0
- package/dist/commands/execution/review-code.d.ts.map +1 -0
- package/dist/commands/execution/write-docs.d.ts +94 -0
- package/dist/commands/execution/write-docs.d.ts.map +1 -0
- package/dist/commands/governance/approve.d.ts +80 -0
- package/dist/commands/governance/approve.d.ts.map +1 -0
- package/dist/commands/intelligence/blast-radius.d.ts +67 -0
- package/dist/commands/intelligence/blast-radius.d.ts.map +1 -0
- package/dist/commands/intelligence/impact-radar.d.ts +71 -0
- package/dist/commands/intelligence/impact-radar.d.ts.map +1 -0
- package/dist/commands/intelligence/intelligence.test.d.ts +2 -0
- package/dist/commands/intelligence/intelligence.test.d.ts.map +1 -0
- package/dist/commands/intelligence/regression-predict.d.ts +75 -0
- package/dist/commands/intelligence/regression-predict.d.ts.map +1 -0
- package/dist/commands/intelligence/review-route.d.ts +65 -0
- package/dist/commands/intelligence/review-route.d.ts.map +1 -0
- package/dist/commands/intelligence/test-gap.d.ts +73 -0
- package/dist/commands/intelligence/test-gap.d.ts.map +1 -0
- package/dist/commands/intelligence/translate-intent.d.ts +87 -0
- package/dist/commands/intelligence/translate-intent.d.ts.map +1 -0
- package/dist/commands/intelligence/volatility-map-cmd.d.ts +68 -0
- package/dist/commands/intelligence/volatility-map-cmd.d.ts.map +1 -0
- package/dist/commands/planning/ask.d.ts +62 -0
- package/dist/commands/planning/ask.d.ts.map +1 -0
- package/dist/commands/planning/ask.test.d.ts +2 -0
- package/dist/commands/planning/ask.test.d.ts.map +1 -0
- package/dist/commands/planning/dashboard.d.ts +30 -0
- package/dist/commands/planning/dashboard.d.ts.map +1 -0
- package/dist/commands/planning/discuss.d.ts +39 -0
- package/dist/commands/planning/discuss.d.ts.map +1 -0
- package/dist/commands/planning/plan.d.ts +67 -0
- package/dist/commands/planning/plan.d.ts.map +1 -0
- package/dist/commands/planning/roadmap.d.ts +105 -0
- package/dist/commands/planning/roadmap.d.ts.map +1 -0
- package/dist/commands/setup/doctor.d.ts +10 -0
- package/dist/commands/setup/doctor.d.ts.map +1 -0
- package/dist/commands/setup/map-codebase.d.ts +62 -0
- package/dist/commands/setup/map-codebase.d.ts.map +1 -0
- package/dist/commands/setup/new-project.d.ts +19 -0
- package/dist/commands/setup/new-project.d.ts.map +1 -0
- package/dist/commands/setup/settings.d.ts +57 -0
- package/dist/commands/setup/settings.d.ts.map +1 -0
- package/dist/commands/state/checkpoint.d.ts +27 -0
- package/dist/commands/state/checkpoint.d.ts.map +1 -0
- package/dist/commands/state/multi-repo.d.ts +63 -0
- package/dist/commands/state/multi-repo.d.ts.map +1 -0
- package/dist/commands/state/progress.d.ts +57 -0
- package/dist/commands/state/progress.d.ts.map +1 -0
- package/dist/commands/state/resume.d.ts +11 -0
- package/dist/commands/state/resume.d.ts.map +1 -0
- package/dist/commands/state/workspace-commands.d.ts +207 -0
- package/dist/commands/state/workspace-commands.d.ts.map +1 -0
- package/dist/dashboard/lib/port-finder.d.ts +10 -0
- package/dist/dashboard/lib/port-finder.d.ts.map +1 -0
- package/dist/dashboard/lib/port-finder.test.d.ts +2 -0
- package/dist/dashboard/lib/port-finder.test.d.ts.map +1 -0
- package/dist/dashboard/lib/state-reader.d.ts +3 -0
- package/dist/dashboard/lib/state-reader.d.ts.map +1 -0
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.mjs +13649 -0
- package/dist/dashboard/types.d.ts +72 -0
- package/dist/dashboard/types.d.ts.map +1 -0
- package/dist/dashboard/views/index.ejs +391 -0
- package/dist/dashboard/views/partials/blockers.ejs +10 -0
- package/dist/dashboard/views/partials/header.ejs +20 -0
- package/dist/dashboard/views/partials/phase-timeline.ejs +40 -0
- package/dist/dashboard/views/partials/progress.ejs +12 -0
- package/dist/hooks/approval-hook.d.ts +13 -0
- package/dist/hooks/approval-hook.d.ts.map +1 -0
- package/dist/hooks/compaction-hook.d.ts +23 -0
- package/dist/hooks/compaction-hook.d.ts.map +1 -0
- package/dist/hooks/context-window-monitor.d.ts +21 -0
- package/dist/hooks/context-window-monitor.d.ts.map +1 -0
- package/dist/hooks/decision-trace-hook.d.ts +13 -0
- package/dist/hooks/decision-trace-hook.d.ts.map +1 -0
- package/dist/hooks/file-tracker.d.ts +29 -0
- package/dist/hooks/file-tracker.d.ts.map +1 -0
- package/dist/hooks/guard-rails.d.ts +33 -0
- package/dist/hooks/guard-rails.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/notifications.d.ts +21 -0
- package/dist/hooks/notifications.d.ts.map +1 -0
- package/dist/hooks/patch-trust.d.ts +27 -0
- package/dist/hooks/patch-trust.d.ts.map +1 -0
- package/dist/hooks/patch-trust.test.d.ts +2 -0
- package/dist/hooks/patch-trust.test.d.ts.map +1 -0
- package/dist/hooks/session-events.d.ts +8 -0
- package/dist/hooks/session-events.d.ts.map +1 -0
- package/dist/hooks/session-idle-hook.d.ts +21 -0
- package/dist/hooks/session-idle-hook.d.ts.map +1 -0
- package/dist/hooks/session-start.d.ts +10 -0
- package/dist/hooks/session-start.d.ts.map +1 -0
- package/dist/hooks/shell-env-hook.d.ts +21 -0
- package/dist/hooks/shell-env-hook.d.ts.map +1 -0
- package/dist/hooks/telemetry-hook.d.ts +25 -0
- package/dist/hooks/telemetry-hook.d.ts.map +1 -0
- package/dist/hooks/todo-hook.d.ts +25 -0
- package/dist/hooks/todo-hook.d.ts.map +1 -0
- package/dist/hooks/tool-guard.d.ts +41 -0
- package/dist/hooks/tool-guard.d.ts.map +1 -0
- package/dist/hooks/tool-guard.test.d.ts +2 -0
- package/dist/hooks/tool-guard.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6639 -0
- package/dist/lib/confirmation.d.ts +20 -0
- package/dist/lib/confirmation.d.ts.map +1 -0
- package/dist/lib/impact-radar.d.ts +35 -0
- package/dist/lib/impact-radar.d.ts.map +1 -0
- package/dist/lib/signatures.d.ts +12 -0
- package/dist/lib/signatures.d.ts.map +1 -0
- package/dist/lib/timestamps.d.ts +23 -0
- package/dist/lib/timestamps.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +20 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/services/agent-performance.d.ts +29 -0
- package/dist/services/agent-performance.d.ts.map +1 -0
- package/dist/services/approval-manager.d.ts +30 -0
- package/dist/services/approval-manager.d.ts.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/model-router.d.ts +35 -0
- package/dist/services/model-router.d.ts.map +1 -0
- package/dist/services/policy-compiler.d.ts +27 -0
- package/dist/services/policy-compiler.d.ts.map +1 -0
- package/dist/services/run-trace.d.ts +34 -0
- package/dist/services/run-trace.d.ts.map +1 -0
- package/dist/services/services.test.d.ts +2 -0
- package/dist/services/services.test.d.ts.map +1 -0
- package/dist/services/telemetry.d.ts +34 -0
- package/dist/services/telemetry.d.ts.map +1 -0
- package/dist/tools/agent-dispatch.test.d.ts +2 -0
- package/dist/tools/agent-dispatch.test.d.ts.map +1 -0
- package/dist/tools/codebase-state.d.ts +4 -0
- package/dist/tools/codebase-state.d.ts.map +1 -0
- package/dist/tools/context-generator.d.ts +3 -0
- package/dist/tools/context-generator.d.ts.map +1 -0
- package/dist/tools/council.d.ts +4 -0
- package/dist/tools/council.d.ts.map +1 -0
- package/dist/tools/decision-trace.d.ts +16 -0
- package/dist/tools/decision-trace.d.ts.map +1 -0
- package/dist/tools/delegate.d.ts +4 -0
- package/dist/tools/delegate.d.ts.map +1 -0
- package/dist/tools/failure-replay.d.ts +19 -0
- package/dist/tools/failure-replay.d.ts.map +1 -0
- package/dist/tools/failure-replay.test.d.ts +2 -0
- package/dist/tools/failure-replay.test.d.ts.map +1 -0
- package/dist/tools/hash-edit.d.ts +3 -0
- package/dist/tools/hash-edit.d.ts.map +1 -0
- package/dist/tools/planning-state-lib.d.ts +65 -0
- package/dist/tools/planning-state-lib.d.ts.map +1 -0
- package/dist/tools/planning-state.d.ts +3 -0
- package/dist/tools/planning-state.d.ts.map +1 -0
- package/dist/tools/policy-engine.d.ts +19 -0
- package/dist/tools/policy-engine.d.ts.map +1 -0
- package/dist/tools/repo-memory.d.ts +20 -0
- package/dist/tools/repo-memory.d.ts.map +1 -0
- package/dist/tools/repo-memory.test.d.ts +2 -0
- package/dist/tools/repo-memory.test.d.ts.map +1 -0
- package/dist/tools/run-parallel.d.ts +4 -0
- package/dist/tools/run-parallel.d.ts.map +1 -0
- package/dist/tools/run-pipeline.d.ts +4 -0
- package/dist/tools/run-pipeline.d.ts.map +1 -0
- package/dist/tools/volatility-map.d.ts +18 -0
- package/dist/tools/volatility-map.d.ts.map +1 -0
- package/dist/tools/volatility-map.test.d.ts +2 -0
- package/dist/tools/volatility-map.test.d.ts.map +1 -0
- package/dist/tools/workspace-state.d.ts +3 -0
- package/dist/tools/workspace-state.d.ts.map +1 -0
- package/docs/USER_GUIDE.md +20 -0
- package/docs/agents.md +562 -0
- package/docs/best-practices.md +47 -0
- package/docs/command-migration.md +175 -0
- package/docs/commands/fd-analyze-change.md +107 -0
- package/docs/commands/fd-ask.md +51 -0
- package/docs/commands/fd-checkpoint.md +10 -0
- package/docs/commands/fd-dashboard.md +11 -0
- package/docs/commands/fd-deploy-check.md +11 -0
- package/docs/commands/fd-discuss.md +28 -0
- package/docs/commands/fd-evaluate-risk.md +134 -0
- package/docs/commands/fd-fix-bug.md +24 -0
- package/docs/commands/fd-guarded-edit.md +105 -0
- package/docs/commands/fd-map-codebase.md +27 -0
- package/docs/commands/fd-multi-repo.md +63 -0
- package/docs/commands/fd-new-feature.md +25 -0
- package/docs/commands/fd-new-project.md +24 -0
- package/docs/commands/fd-plan.md +33 -0
- package/docs/commands/fd-progress.md +11 -0
- package/docs/commands/fd-resume.md +10 -0
- package/docs/commands/fd-review-code.md +29 -0
- package/docs/commands/fd-roadmap.md +10 -0
- package/docs/commands/fd-settings.md +10 -0
- package/docs/commands/fd-write-docs.md +10 -0
- package/docs/commands.md +476 -0
- package/docs/configuration.md +211 -0
- package/docs/feature-integration-architecture.md +255 -0
- package/docs/index.md +75 -0
- package/docs/installation.md +134 -0
- package/docs/intelligence.md +294 -0
- package/docs/multi-repo.md +201 -0
- package/docs/notifications.md +170 -0
- package/docs/parallel-execution.md +227 -0
- package/docs/quick-start.md +174 -0
- package/docs/rules.md +459 -0
- package/docs/skills.md +408 -0
- package/docs/workflows.md +376 -0
- package/package.json +58 -0
- package/postinstall.mjs +102 -0
- package/src/rules/README.md +37 -0
- package/src/rules/common/agent-orchestration.md +86 -0
- package/src/rules/common/coding-style.md +120 -0
- package/src/rules/common/git-workflow.md +77 -0
- package/src/rules/common/security.md +94 -0
- package/src/rules/common/testing.md +105 -0
- package/src/rules/golang/patterns.md +187 -0
- package/src/rules/java/patterns.md +204 -0
- package/src/rules/python/patterns.md +141 -0
- package/src/rules/rust/patterns.md +210 -0
- package/src/rules/typescript/patterns.md +168 -0
- package/src/skills/api-design/SKILL.md +143 -0
- package/src/skills/arch-constraint-guard/SKILL.md +61 -0
- package/src/skills/blast-radius-preview/SKILL.md +65 -0
- package/src/skills/change-impact-radar/SKILL.md +63 -0
- package/src/skills/code-review/SKILL.md +108 -0
- package/src/skills/code-tour/SKILL.md +101 -0
- package/src/skills/codebase-mapping/SKILL.md +87 -0
- package/src/skills/codebase-onboarding/SKILL.md +133 -0
- package/src/skills/confidence-aware-planning/SKILL.md +67 -0
- package/src/skills/context-load/SKILL.md +63 -0
- package/src/skills/debug-flow/SKILL.md +75 -0
- package/src/skills/decision-trace/SKILL.md +72 -0
- package/src/skills/dependency-audit/SKILL.md +126 -0
- package/src/skills/deploy-check/SKILL.md +87 -0
- package/src/skills/documentation-writer/SKILL.md +154 -0
- package/src/skills/failure-replay-engine/SKILL.md +59 -0
- package/src/skills/git-release/SKILL.md +94 -0
- package/src/skills/git-workflow/SKILL.md +177 -0
- package/src/skills/golang-patterns/SKILL.md +511 -0
- package/src/skills/human-review-routing/SKILL.md +65 -0
- package/src/skills/intent-translator/SKILL.md +57 -0
- package/src/skills/java-patterns/SKILL.md +479 -0
- package/src/skills/multi-repo/SKILL.md +187 -0
- package/src/skills/parallel-execute/SKILL.md +92 -0
- package/src/skills/patch-trust-score/SKILL.md +44 -0
- package/src/skills/performance-profiling/SKILL.md +153 -0
- package/src/skills/plan-task/SKILL.md +101 -0
- package/src/skills/python-patterns/SKILL.md +529 -0
- package/src/skills/refactor-guide/SKILL.md +117 -0
- package/src/skills/regression-prediction/SKILL.md +57 -0
- package/src/skills/repo-memory-graph/SKILL.md +49 -0
- package/src/skills/rust-patterns/SKILL.md +492 -0
- package/src/skills/security-scan/SKILL.md +91 -0
- package/src/skills/self-healing-policies/SKILL.md +76 -0
- package/src/skills/tdd-workflow/SKILL.md +126 -0
- package/src/skills/test-coverage/SKILL.md +94 -0
- package/src/skills/test-gap-detector/SKILL.md +58 -0
- package/src/skills/volatility-map/SKILL.md +52 -0
- package/src/workflows/debug-flow.md +119 -0
- package/src/workflows/deploy-check-flow.md +98 -0
- package/src/workflows/discuss-flow.md +97 -0
- package/src/workflows/execute-flow.md +233 -0
- package/src/workflows/execute-phase.md +142 -0
- package/src/workflows/fix-bug-flow.md +210 -0
- package/src/workflows/map-codebase-flow.md +92 -0
- package/src/workflows/multi-repo-flow.md +226 -0
- package/src/workflows/parallel-execution-flow.md +236 -0
- package/src/workflows/plan-flow.md +126 -0
- package/src/workflows/plan-phase.md +101 -0
- package/src/workflows/refactor-flow.md +122 -0
- package/src/workflows/review-code-flow.md +105 -0
- package/src/workflows/spec-driven-flow.md +43 -0
- package/src/workflows/write-docs-flow.md +95 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-patterns
|
|
3
|
+
description: Python-specific idioms and patterns covering type hints, dataclasses, async/await, generators, testing with pytest, and common pitfalls. Activate when writing or reviewing Python code.
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Python Patterns Skill
|
|
8
|
+
|
|
9
|
+
Idiomatic Python for production-grade code. Covers modern Python 3.10+ practices.
|
|
10
|
+
|
|
11
|
+
## When to Activate
|
|
12
|
+
|
|
13
|
+
Activate when:
|
|
14
|
+
- Writing new Python modules or packages
|
|
15
|
+
- Reviewing Python code for correctness and idiom
|
|
16
|
+
- Deciding between data modeling approaches (dataclass vs TypedDict vs Pydantic)
|
|
17
|
+
- Designing async services or background workers
|
|
18
|
+
- Setting up testing infrastructure
|
|
19
|
+
|
|
20
|
+
## Type Hints
|
|
21
|
+
|
|
22
|
+
Python's type system (PEP 484, 526, 544) makes code self-documenting and enables static analysis with mypy or pyright.
|
|
23
|
+
|
|
24
|
+
### Basic Annotations
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# Variables (PEP 526)
|
|
28
|
+
count: int = 0
|
|
29
|
+
names: list[str] = []
|
|
30
|
+
mapping: dict[str, int] = {}
|
|
31
|
+
|
|
32
|
+
# Functions — always annotate public API
|
|
33
|
+
def greet(name: str, times: int = 1) -> str:
|
|
34
|
+
return (f"Hello, {name}!\n" * times).rstrip()
|
|
35
|
+
|
|
36
|
+
# Optional and Union (Python 3.10+ union syntax preferred)
|
|
37
|
+
def find_user(user_id: int) -> "User | None":
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
# Use TypeAlias for reused complex types
|
|
41
|
+
type UserId = int # Python 3.12+
|
|
42
|
+
UserId = NewType("UserId", int) # pre-3.12
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Protocols (PEP 544) — Structural Subtyping
|
|
46
|
+
|
|
47
|
+
Prefer Protocol over ABC when you don't control the implementor.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from typing import Protocol, runtime_checkable
|
|
51
|
+
|
|
52
|
+
@runtime_checkable
|
|
53
|
+
class Serializable(Protocol):
|
|
54
|
+
def to_dict(self) -> dict[str, object]: ...
|
|
55
|
+
|
|
56
|
+
def save(obj: Serializable) -> None:
|
|
57
|
+
data = obj.to_dict()
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
# Any class with to_dict() satisfies Serializable — no inheritance required
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Generics
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from typing import TypeVar, Generic
|
|
67
|
+
|
|
68
|
+
T = TypeVar("T")
|
|
69
|
+
|
|
70
|
+
class Stack(Generic[T]):
|
|
71
|
+
def __init__(self) -> None:
|
|
72
|
+
self._items: list[T] = []
|
|
73
|
+
|
|
74
|
+
def push(self, item: T) -> None:
|
|
75
|
+
self._items.append(item)
|
|
76
|
+
|
|
77
|
+
def pop(self) -> T:
|
|
78
|
+
return self._items.pop()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Data Modeling: Dataclass vs TypedDict vs Pydantic
|
|
82
|
+
|
|
83
|
+
Choose based on where the data lives and what guarantees you need.
|
|
84
|
+
|
|
85
|
+
### Dataclass — in-memory objects with behavior
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from dataclasses import dataclass, field
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class Order:
|
|
92
|
+
id: str
|
|
93
|
+
items: list[str] = field(default_factory=list)
|
|
94
|
+
total: float = 0.0
|
|
95
|
+
|
|
96
|
+
def add_item(self, item: str, price: float) -> None:
|
|
97
|
+
self.items.append(item)
|
|
98
|
+
self.total += price
|
|
99
|
+
|
|
100
|
+
# Use @dataclass(frozen=True) for immutable value objects
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class Money:
|
|
103
|
+
amount: int # stored in cents
|
|
104
|
+
currency: str = "USD"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### TypedDict — typed dictionaries, no runtime overhead
|
|
108
|
+
|
|
109
|
+
Best for function signatures that accept/return dict-shaped data (JSON responses, kwargs).
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from typing import TypedDict, NotRequired
|
|
113
|
+
|
|
114
|
+
class UserPayload(TypedDict):
|
|
115
|
+
id: str
|
|
116
|
+
email: str
|
|
117
|
+
name: NotRequired[str] # optional key
|
|
118
|
+
|
|
119
|
+
def process(payload: UserPayload) -> None:
|
|
120
|
+
print(payload["id"]) # static checker knows the type
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Pydantic — validation at the boundary
|
|
124
|
+
|
|
125
|
+
Use at I/O boundaries: API request bodies, config files, deserialized data.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pydantic import BaseModel, Field, field_validator
|
|
129
|
+
|
|
130
|
+
class CreateUserRequest(BaseModel):
|
|
131
|
+
email: str
|
|
132
|
+
age: int = Field(gt=0, lt=150)
|
|
133
|
+
name: str = Field(min_length=1, max_length=100)
|
|
134
|
+
|
|
135
|
+
@field_validator("email")
|
|
136
|
+
@classmethod
|
|
137
|
+
def email_must_have_at(cls, v: str) -> str:
|
|
138
|
+
if "@" not in v:
|
|
139
|
+
raise ValueError("not a valid email")
|
|
140
|
+
return v.lower()
|
|
141
|
+
|
|
142
|
+
# Pydantic raises ValidationError with structured detail on bad input
|
|
143
|
+
req = CreateUserRequest(email="ALICE@EXAMPLE.COM", age=30, name="Alice")
|
|
144
|
+
# req.email == "alice@example.com"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Decision rule:** dataclass for domain objects with behavior → TypedDict for dicts that stay dicts → Pydantic for external input validation.
|
|
148
|
+
|
|
149
|
+
## Context Managers
|
|
150
|
+
|
|
151
|
+
The `with` statement guarantees cleanup whether or not an exception occurs.
|
|
152
|
+
|
|
153
|
+
### Using `contextlib`
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from contextlib import contextmanager, asynccontextmanager
|
|
157
|
+
|
|
158
|
+
@contextmanager
|
|
159
|
+
def managed_connection(dsn: str):
|
|
160
|
+
conn = connect(dsn)
|
|
161
|
+
try:
|
|
162
|
+
yield conn
|
|
163
|
+
finally:
|
|
164
|
+
conn.close()
|
|
165
|
+
|
|
166
|
+
# Usage
|
|
167
|
+
with managed_connection("postgresql://...") as conn:
|
|
168
|
+
conn.execute("SELECT 1")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Class-Based Context Managers
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
class Timer:
|
|
175
|
+
def __enter__(self) -> "Timer":
|
|
176
|
+
import time
|
|
177
|
+
self._start = time.perf_counter()
|
|
178
|
+
return self
|
|
179
|
+
|
|
180
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
181
|
+
self.elapsed = time.perf_counter() - self._start
|
|
182
|
+
return False # don't suppress exceptions
|
|
183
|
+
|
|
184
|
+
with Timer() as t:
|
|
185
|
+
expensive_operation()
|
|
186
|
+
print(f"Took {t.elapsed:.3f}s")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Suppressing Exceptions
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from contextlib import suppress
|
|
193
|
+
|
|
194
|
+
with suppress(FileNotFoundError):
|
|
195
|
+
Path("optional.txt").unlink()
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Generator and Iterator Patterns
|
|
199
|
+
|
|
200
|
+
Generators yield values lazily — use them for large sequences or pipelines.
|
|
201
|
+
|
|
202
|
+
### Basic Generator
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
def read_chunks(path: str, size: int = 8192):
|
|
206
|
+
with open(path, "rb") as f:
|
|
207
|
+
while chunk := f.read(size):
|
|
208
|
+
yield chunk
|
|
209
|
+
|
|
210
|
+
# Entire file never loaded at once
|
|
211
|
+
for chunk in read_chunks("large.bin"):
|
|
212
|
+
process(chunk)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Generator Pipelines
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
def parse_lines(lines):
|
|
219
|
+
for line in lines:
|
|
220
|
+
yield line.strip()
|
|
221
|
+
|
|
222
|
+
def filter_comments(lines):
|
|
223
|
+
for line in lines:
|
|
224
|
+
if not line.startswith("#"):
|
|
225
|
+
yield line
|
|
226
|
+
|
|
227
|
+
def process_file(path: str):
|
|
228
|
+
raw = open(path)
|
|
229
|
+
stripped = parse_lines(raw)
|
|
230
|
+
meaningful = filter_comments(stripped)
|
|
231
|
+
return meaningful # lazy, no I/O yet
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### `itertools` for Composition
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
import itertools
|
|
238
|
+
|
|
239
|
+
# Flatten a list of lists
|
|
240
|
+
flat = list(itertools.chain.from_iterable([[1, 2], [3, 4]]))
|
|
241
|
+
|
|
242
|
+
# Sliding window
|
|
243
|
+
def windows(iterable, n):
|
|
244
|
+
iters = itertools.tee(iterable, n)
|
|
245
|
+
for i, it in enumerate(iters):
|
|
246
|
+
next(itertools.islice(it, i, i), None)
|
|
247
|
+
return zip(*iters)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Asyncio Patterns
|
|
251
|
+
|
|
252
|
+
Use `async/await` for I/O-bound concurrency. Don't use it for CPU-bound work (use `multiprocessing` instead).
|
|
253
|
+
|
|
254
|
+
### Async HTTP: httpx vs aiohttp
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
# httpx — recommended for most cases (sync and async API, HTTP/2)
|
|
258
|
+
import httpx
|
|
259
|
+
|
|
260
|
+
async def fetch_user(user_id: int) -> dict:
|
|
261
|
+
async with httpx.AsyncClient() as client:
|
|
262
|
+
resp = await client.get(f"https://api.example.com/users/{user_id}")
|
|
263
|
+
resp.raise_for_status()
|
|
264
|
+
return resp.json()
|
|
265
|
+
|
|
266
|
+
# aiohttp — use when you need streaming or websockets
|
|
267
|
+
import aiohttp
|
|
268
|
+
|
|
269
|
+
async def stream_large_file(url: str):
|
|
270
|
+
async with aiohttp.ClientSession() as session:
|
|
271
|
+
async with session.get(url) as resp:
|
|
272
|
+
async for chunk in resp.content.iter_chunked(8192):
|
|
273
|
+
process(chunk)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Concurrent Tasks
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
import asyncio
|
|
280
|
+
|
|
281
|
+
# Run independent I/O operations concurrently
|
|
282
|
+
async def fetch_all(ids: list[int]) -> list[dict]:
|
|
283
|
+
async with httpx.AsyncClient() as client:
|
|
284
|
+
tasks = [fetch_one(client, id) for id in ids]
|
|
285
|
+
return await asyncio.gather(*tasks)
|
|
286
|
+
|
|
287
|
+
# Timeout and cancellation
|
|
288
|
+
async def with_timeout(coro, seconds: float):
|
|
289
|
+
try:
|
|
290
|
+
return await asyncio.wait_for(coro, timeout=seconds)
|
|
291
|
+
except asyncio.TimeoutError:
|
|
292
|
+
raise TimeoutError(f"operation exceeded {seconds}s")
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Event Loop Best Practices
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
# Don't call asyncio.get_event_loop() in new code — use asyncio.run()
|
|
299
|
+
if __name__ == "__main__":
|
|
300
|
+
asyncio.run(main())
|
|
301
|
+
|
|
302
|
+
# For libraries, accept a running loop rather than creating one
|
|
303
|
+
async def library_function() -> None:
|
|
304
|
+
loop = asyncio.get_running_loop() # raises if not inside async context
|
|
305
|
+
...
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Comprehensions — When to Use vs Loops
|
|
309
|
+
|
|
310
|
+
Comprehensions communicate intent at a glance. Loops are clearer for side effects.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# ✅ Comprehension: transform + filter, no side effects
|
|
314
|
+
active_names = [u.name for u in users if u.is_active]
|
|
315
|
+
index = {u.id: u for u in users}
|
|
316
|
+
unique_tags = {tag for post in posts for tag in post.tags}
|
|
317
|
+
|
|
318
|
+
# ✅ Generator expression: same as list comprehension but lazy
|
|
319
|
+
total = sum(item.price for item in cart)
|
|
320
|
+
|
|
321
|
+
# ❌ Comprehension with side effects — use a loop
|
|
322
|
+
[print(x) for x in items] # bad
|
|
323
|
+
for x in items: # good
|
|
324
|
+
print(x)
|
|
325
|
+
|
|
326
|
+
# ❌ Deeply nested comprehensions — use a loop
|
|
327
|
+
matrix = [[col * row for col in range(5)] for row in range(5)] # fine
|
|
328
|
+
# but three levels deep: write a loop
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Exception Hierarchy and Custom Exceptions
|
|
332
|
+
|
|
333
|
+
Define a base exception per module/package and branch from it.
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
# exceptions.py
|
|
337
|
+
class AppError(Exception):
|
|
338
|
+
"""Base for all application errors."""
|
|
339
|
+
|
|
340
|
+
class NotFoundError(AppError):
|
|
341
|
+
def __init__(self, resource: str, id: object) -> None:
|
|
342
|
+
super().__init__(f"{resource} {id!r} not found")
|
|
343
|
+
self.resource = resource
|
|
344
|
+
self.id = id
|
|
345
|
+
|
|
346
|
+
class ValidationError(AppError):
|
|
347
|
+
def __init__(self, field: str, message: str) -> None:
|
|
348
|
+
super().__init__(f"{field}: {message}")
|
|
349
|
+
self.field = field
|
|
350
|
+
|
|
351
|
+
# Catching
|
|
352
|
+
try:
|
|
353
|
+
get_user(user_id)
|
|
354
|
+
except NotFoundError as exc:
|
|
355
|
+
return 404, {"error": str(exc)}
|
|
356
|
+
except AppError as exc:
|
|
357
|
+
logger.exception("unexpected app error")
|
|
358
|
+
return 500, {"error": "internal error"}
|
|
359
|
+
|
|
360
|
+
# Exception chaining — always preserve cause
|
|
361
|
+
try:
|
|
362
|
+
result = json.loads(raw)
|
|
363
|
+
except json.JSONDecodeError as exc:
|
|
364
|
+
raise ValidationError("body", "invalid JSON") from exc
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Dependency Management
|
|
368
|
+
|
|
369
|
+
### pyproject.toml (PEP 518/621)
|
|
370
|
+
|
|
371
|
+
```toml
|
|
372
|
+
[project]
|
|
373
|
+
name = "my-service"
|
|
374
|
+
version = "1.0.0"
|
|
375
|
+
requires-python = ">=3.11"
|
|
376
|
+
dependencies = [
|
|
377
|
+
"httpx>=0.27",
|
|
378
|
+
"pydantic>=2.0",
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
[project.optional-dependencies]
|
|
382
|
+
dev = ["pytest>=8", "mypy", "ruff"]
|
|
383
|
+
|
|
384
|
+
[build-system]
|
|
385
|
+
requires = ["hatchling"]
|
|
386
|
+
build-backend = "hatchling.build"
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### uv — fast package manager
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
# Create and activate venv
|
|
393
|
+
uv venv && source .venv/bin/activate
|
|
394
|
+
|
|
395
|
+
# Install project with dev deps
|
|
396
|
+
uv pip install -e ".[dev]"
|
|
397
|
+
|
|
398
|
+
# Add a dependency (updates pyproject.toml)
|
|
399
|
+
uv add httpx
|
|
400
|
+
|
|
401
|
+
# Lock for reproducibility
|
|
402
|
+
uv lock
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### poetry — alternative with integrated lock file
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
poetry new my-project
|
|
409
|
+
poetry add httpx pydantic
|
|
410
|
+
poetry add --group dev pytest mypy
|
|
411
|
+
poetry run pytest
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Testing with pytest
|
|
415
|
+
|
|
416
|
+
### Fixtures
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
import pytest
|
|
420
|
+
from myapp.db import Database
|
|
421
|
+
|
|
422
|
+
@pytest.fixture
|
|
423
|
+
def db():
|
|
424
|
+
database = Database(":memory:")
|
|
425
|
+
database.migrate()
|
|
426
|
+
yield database
|
|
427
|
+
database.close()
|
|
428
|
+
|
|
429
|
+
@pytest.fixture
|
|
430
|
+
def user(db):
|
|
431
|
+
return db.create_user(email="alice@example.com", name="Alice")
|
|
432
|
+
|
|
433
|
+
def test_user_lookup(db, user):
|
|
434
|
+
found = db.get_user(user.id)
|
|
435
|
+
assert found.email == user.email
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Parametrize
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
@pytest.mark.parametrize("email,valid", [
|
|
442
|
+
("alice@example.com", True),
|
|
443
|
+
("no-at-sign", False),
|
|
444
|
+
("@nodomain", False),
|
|
445
|
+
("spaces @x.com", False),
|
|
446
|
+
])
|
|
447
|
+
def test_email_validation(email: str, valid: bool) -> None:
|
|
448
|
+
assert validate_email(email) == valid
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Mocking
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
455
|
+
|
|
456
|
+
def test_send_notification(mocker): # with pytest-mock
|
|
457
|
+
mock_send = mocker.patch("myapp.email.send_email")
|
|
458
|
+
notify_user(user_id=1)
|
|
459
|
+
mock_send.assert_called_once_with(
|
|
460
|
+
to="alice@example.com",
|
|
461
|
+
subject="Welcome",
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
async def test_async_fetch():
|
|
465
|
+
with patch("myapp.client.httpx.AsyncClient") as MockClient:
|
|
466
|
+
instance = MockClient.return_value.__aenter__.return_value
|
|
467
|
+
instance.get.return_value = AsyncMock(
|
|
468
|
+
status_code=200,
|
|
469
|
+
json=lambda: {"id": 1},
|
|
470
|
+
)
|
|
471
|
+
result = await fetch_user(1)
|
|
472
|
+
assert result["id"] == 1
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Common Pitfalls
|
|
476
|
+
|
|
477
|
+
### Mutable Default Arguments
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
# ❌ The list is created ONCE and shared across all calls
|
|
481
|
+
def append_to(item, lst=[]):
|
|
482
|
+
lst.append(item)
|
|
483
|
+
return lst
|
|
484
|
+
|
|
485
|
+
# ✅ Use None as sentinel
|
|
486
|
+
def append_to(item, lst=None):
|
|
487
|
+
if lst is None:
|
|
488
|
+
lst = []
|
|
489
|
+
lst.append(item)
|
|
490
|
+
return lst
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Late Binding in Closures
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
# ❌ All lambdas capture the same `i` variable
|
|
497
|
+
funcs = [lambda: i for i in range(5)]
|
|
498
|
+
# funcs[0]() == 4 (not 0!)
|
|
499
|
+
|
|
500
|
+
# ✅ Bind at definition time with default argument
|
|
501
|
+
funcs = [lambda i=i: i for i in range(5)]
|
|
502
|
+
# funcs[0]() == 0
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### The GIL and CPU-Bound Work
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
# The GIL prevents true parallel execution of Python bytecode.
|
|
509
|
+
# For CPU-bound work, use multiprocessing or ProcessPoolExecutor.
|
|
510
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
511
|
+
|
|
512
|
+
def cpu_bound(n: int) -> int:
|
|
513
|
+
return sum(range(n))
|
|
514
|
+
|
|
515
|
+
with ProcessPoolExecutor() as pool:
|
|
516
|
+
results = list(pool.map(cpu_bound, [10**7, 10**7, 10**7]))
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Circular Imports
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
# ❌ module_a imports module_b, module_b imports module_a at module level
|
|
523
|
+
# ✅ Move the import inside the function that needs it
|
|
524
|
+
def get_thing():
|
|
525
|
+
from myapp.other_module import Thing # deferred import
|
|
526
|
+
return Thing()
|
|
527
|
+
|
|
528
|
+
# ✅ Or restructure: extract shared types into a third module
|
|
529
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refactor-guide
|
|
3
|
+
description: Safe refactoring workflow. Ensure tests pass before and after, change structure without changing behavior, no public API breakage. Use for code maintenance and cleanup.
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Refactor Guide Skill
|
|
8
|
+
|
|
9
|
+
Changes structure without changing behavior. One transformation at a time. Tests must stay green throughout.
|
|
10
|
+
|
|
11
|
+
## When to Activate
|
|
12
|
+
|
|
13
|
+
Activate when:
|
|
14
|
+
- A function is over 50 lines
|
|
15
|
+
- A file is over 800 lines
|
|
16
|
+
- There is significant code duplication
|
|
17
|
+
- Variable or function names are misleading
|
|
18
|
+
- You are preparing code for a new feature
|
|
19
|
+
|
|
20
|
+
## Core Principles
|
|
21
|
+
|
|
22
|
+
- Tests green before you start — if not, fix tests first
|
|
23
|
+
- One transformation per commit
|
|
24
|
+
- No features in refactor commits
|
|
25
|
+
- If any test breaks, undo and try a smaller step
|
|
26
|
+
|
|
27
|
+
## Safe Refactoring Process
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
1. npm test → must be GREEN before starting
|
|
31
|
+
|
|
32
|
+
2. Apply ONE transformation
|
|
33
|
+
(extract function, rename, move module — one thing only)
|
|
34
|
+
|
|
35
|
+
3. npm test → must still be GREEN
|
|
36
|
+
|
|
37
|
+
4. git commit -m "refactor: [description]"
|
|
38
|
+
|
|
39
|
+
5. Repeat from step 2
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Extract Function Pattern
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// ❌ Before — inline, hard to test independently
|
|
46
|
+
function createOrder(items: Item[], userId: string) {
|
|
47
|
+
if (!items || items.length === 0) {
|
|
48
|
+
throw new Error('Order must have items');
|
|
49
|
+
}
|
|
50
|
+
const total = items.reduce((s, i) => s + i.price * i.qty, 0);
|
|
51
|
+
if (total > 10000) {
|
|
52
|
+
throw new Error('Order total exceeds limit');
|
|
53
|
+
}
|
|
54
|
+
// ... save to DB
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ✅ After — each function has a single responsibility
|
|
58
|
+
function validateOrderItems(items: Item[]): void {
|
|
59
|
+
if (!items || items.length === 0) {
|
|
60
|
+
throw new Error('Order must have items');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function calculateOrderTotal(items: Item[]): number {
|
|
65
|
+
return items.reduce((s, i) => s + i.price * i.qty, 0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function assertTotalWithinLimit(total: number): void {
|
|
69
|
+
if (total > 10000) throw new Error('Order total exceeds limit');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createOrder(items: Item[], userId: string) {
|
|
73
|
+
validateOrderItems(items);
|
|
74
|
+
const total = calculateOrderTotal(items);
|
|
75
|
+
assertTotalWithinLimit(total);
|
|
76
|
+
// ... save to DB
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Extract Variable Pattern
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// ❌ Before — magic numbers and complex expression
|
|
84
|
+
if (user.createdAt < Date.now() - 30 * 24 * 60 * 60 * 1000) { ... }
|
|
85
|
+
|
|
86
|
+
// ✅ After — named intent
|
|
87
|
+
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
88
|
+
const accountAge = Date.now() - user.createdAt;
|
|
89
|
+
const isNewUser = accountAge < THIRTY_DAYS_MS;
|
|
90
|
+
if (isNewUser) { ... }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Danger Signs — Stop Immediately
|
|
94
|
+
|
|
95
|
+
- Tests breaking during refactor → undo, try smaller step
|
|
96
|
+
- Adding a feature while refactoring → separate commit
|
|
97
|
+
- Renaming AND moving in same commit → split into two commits
|
|
98
|
+
- Touching unrelated code → leave it alone
|
|
99
|
+
|
|
100
|
+
## Output Format
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
## Refactor Summary
|
|
104
|
+
|
|
105
|
+
### Transformations (in order applied)
|
|
106
|
+
1. Extracted `validateOrderItems()` — order.ts:23-28
|
|
107
|
+
2. Extracted `calculateOrderTotal()` — order.ts:29-31
|
|
108
|
+
3. Renamed `getData()` → `fetchUserProfile()` — 4 files
|
|
109
|
+
|
|
110
|
+
### Before/After
|
|
111
|
+
- order.ts: 180 lines → 120 lines
|
|
112
|
+
- 2 new unit tests for extracted functions
|
|
113
|
+
|
|
114
|
+
### Test Results
|
|
115
|
+
- Before: 45 tests passing
|
|
116
|
+
- After: 47 tests passing
|
|
117
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: regression-prediction
|
|
3
|
+
description: Estimate the most likely regression categories for a proposed change — performance, auth, schema, UI states, async flows — before merging.
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Regression Prediction
|
|
8
|
+
|
|
9
|
+
Before merging, predict what is most likely to break. Run `/regression-predict` with a description of the change and the files affected.
|
|
10
|
+
|
|
11
|
+
## Regression Categories
|
|
12
|
+
|
|
13
|
+
| Category | Triggered by |
|
|
14
|
+
|----------|-------------|
|
|
15
|
+
| performance | database queries, loops, caching, serialization, lazy-loading |
|
|
16
|
+
| auth | JWT, session, OAuth, RBAC, middleware, token, permission |
|
|
17
|
+
| schema | database migration, model change, field rename, relation change |
|
|
18
|
+
| ui-state | React state, Redux, context, form state, loading/error states |
|
|
19
|
+
| async-flow | Promise, async/await, event emitter, queue, webhook, retry logic |
|
|
20
|
+
| api-contract | Route signature, request/response shape, HTTP status codes |
|
|
21
|
+
| data-integrity | Validation, constraints, null handling, type coercion |
|
|
22
|
+
| security | Input sanitization, XSS, CSRF, injection, file upload |
|
|
23
|
+
| config | Environment variables, feature flags, hardcoded values |
|
|
24
|
+
| i18n | Hardcoded strings, date/time formatting, locale handling |
|
|
25
|
+
|
|
26
|
+
## Prediction Workflow
|
|
27
|
+
|
|
28
|
+
1. Map the changed files to regression categories using keyword detection
|
|
29
|
+
2. Check `.codebase/FAILURES.json` for prior regressions in these files
|
|
30
|
+
3. Weight categories by: keyword match + failure history + test coverage gap
|
|
31
|
+
4. Rank by probability × severity
|
|
32
|
+
5. For each top-3 category, suggest a specific test to catch the regression
|
|
33
|
+
|
|
34
|
+
## Output Format
|
|
35
|
+
|
|
36
|
+
```markdown
|
|
37
|
+
## Regression Prediction Report
|
|
38
|
+
|
|
39
|
+
### Change: [description]
|
|
40
|
+
|
|
41
|
+
| Category | Probability | Severity | Evidence | Suggested Test |
|
|
42
|
+
|----------|------------|---------|---------|----------------|
|
|
43
|
+
| auth | high | critical | JWT logic modified, prior auth failure | Test token expiry boundary |
|
|
44
|
+
| schema | medium | high | Model field added | Test migration rollback |
|
|
45
|
+
| async-flow | low | medium | No async code changed | — |
|
|
46
|
+
|
|
47
|
+
### Top Risk: auth
|
|
48
|
+
[Specific regression scenario and suggested test]
|
|
49
|
+
|
|
50
|
+
### Prediction Confidence: [HIGH / MEDIUM / LOW]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Guidance
|
|
54
|
+
|
|
55
|
+
- High probability + critical severity = do not merge without regression test
|
|
56
|
+
- Use predictions to prioritize what to test BEFORE merging, not after
|
|
57
|
+
- Record confirmed regressions in `.codebase/FAILURES.json` to improve future predictions
|