openhermes 4.1.0 → 4.3.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/ETHOS.md +6 -3
- package/LICENSE +21 -21
- package/README.md +109 -79
- package/bootstrap.ts +214 -8
- package/harness/agents/openhermes.md +45 -55
- package/harness/codex/AUTOPILOT.md +126 -0
- package/harness/codex/CONSTITUTION.md +14 -11
- package/harness/codex/ROUTING.md +35 -70
- package/harness/commands/oh-log.md +18 -0
- package/harness/instructions/RUNTIME.md +27 -52
- package/harness/skills/oh-builder/SKILL.md +13 -8
- package/harness/skills/oh-caveman/SKILL.md +9 -0
- package/harness/skills/oh-expert/SKILL.md +6 -0
- package/harness/skills/oh-facade/SKILL.md +298 -0
- package/harness/skills/oh-freeze/SKILL.md +9 -0
- package/harness/skills/oh-full-output/SKILL.md +81 -0
- package/harness/skills/oh-fusion/SKILL.md +314 -0
- package/harness/skills/oh-gauntlet/SKILL.md +9 -5
- package/harness/skills/oh-grill/SKILL.md +9 -5
- package/harness/skills/oh-guard/SKILL.md +9 -0
- package/harness/skills/oh-handoff/SKILL.md +9 -0
- package/harness/skills/oh-health/SKILL.md +8 -4
- package/harness/skills/oh-init/SKILL.md +28 -94
- package/harness/skills/oh-investigate/SKILL.md +10 -0
- package/harness/skills/oh-issue/SKILL.md +9 -0
- package/harness/skills/oh-learn/SKILL.md +13 -4
- package/harness/skills/oh-manifest/SKILL.md +15 -10
- package/harness/skills/oh-plan-review/SKILL.md +15 -8
- package/harness/skills/oh-planner/SKILL.md +18 -8
- package/harness/skills/oh-prd/SKILL.md +9 -0
- package/harness/skills/oh-refactor/SKILL.md +426 -0
- package/harness/skills/oh-retro/SKILL.md +9 -0
- package/harness/skills/oh-review/SKILL.md +11 -4
- package/harness/skills/oh-security/SKILL.md +4 -0
- package/harness/skills/oh-ship/SKILL.md +10 -0
- package/harness/skills/oh-skill-craft/SKILL.md +88 -0
- package/harness/skills/oh-skills-link/SKILL.md +9 -0
- package/harness/skills/oh-skills-list/SKILL.md +9 -0
- package/harness/skills/oh-triage/SKILL.md +11 -0
- package/lib/harness-resolver.ts +2 -2
- package/lib/logger.ts +7 -1
- package/package.json +6 -3
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oh-learn
|
|
3
3
|
description: "Extract, evolve, and promote session learnings as instincts. Review, search, prune, export."
|
|
4
|
+
tier: 2
|
|
5
|
+
triggers:
|
|
6
|
+
- "learn from session"
|
|
7
|
+
- "extract patterns"
|
|
8
|
+
- "run oh-learn"
|
|
9
|
+
route:
|
|
10
|
+
pass: done
|
|
11
|
+
fail: surface
|
|
12
|
+
blocker: surface
|
|
4
13
|
---
|
|
5
14
|
|
|
6
15
|
# oh-learn
|
|
@@ -9,7 +18,7 @@ Learning engine for the harness. Distills patterns from sessions into **instinct
|
|
|
9
18
|
|
|
10
19
|
## Instinct Data Model
|
|
11
20
|
|
|
12
|
-
Every learning stored as one JSONL line in
|
|
21
|
+
Every learning stored as one JSONL line in `~/.local/share/opencode/openhermes/plans/<project-name>-instincts.jsonl`:
|
|
13
22
|
|
|
14
23
|
```json
|
|
15
24
|
{ "trigger": "situation pattern", "action": "recommended response", "confidence": 0.5, "applications": 1, "successes": 1, "category": "coding", "source": "oh-learn:extract", "ts": "2026-05-15T12:00:00Z" }
|
|
@@ -32,7 +41,7 @@ Mine the current session for reusable patterns.
|
|
|
32
41
|
|
|
33
42
|
1. Scan recent conversation + code changes for repeated decision patterns
|
|
34
43
|
2. For each distinct pattern write an instinct: trigger, action, confidence=0.5, category
|
|
35
|
-
3. Read existing
|
|
44
|
+
3. Read existing `~/.local/share/opencode/openhermes/plans/<project-name>-instincts.jsonl`, check for near-duplicate triggers
|
|
36
45
|
4. If duplicate found: merge — `confidence = max(existing, 0.8 × new)`, increment applications
|
|
37
46
|
5. If new: append line to file
|
|
38
47
|
|
|
@@ -43,7 +52,7 @@ Mine the current session for reusable patterns.
|
|
|
43
52
|
### Evolve
|
|
44
53
|
Cluster related instincts into skill/command/agent candidates.
|
|
45
54
|
|
|
46
|
-
1. Read all instincts from
|
|
55
|
+
1. Read all instincts from `~/.local/share/opencode/openhermes/plans/<project-name>-instincts.jsonl`
|
|
47
56
|
2. Group by `category`, then by trigger topic similarity
|
|
48
57
|
3. **If cluster ≥ 5 instincts AND avg confidence ≥ 0.7** → generate `oh-skill-craft` spec for a new skill
|
|
49
58
|
4. **If cluster 3-4 instincts with confidence ≥ 0.8** → suggest update to existing skill
|
|
@@ -52,7 +61,7 @@ Cluster related instincts into skill/command/agent candidates.
|
|
|
52
61
|
### Promote
|
|
53
62
|
Graduate high-confidence instincts from project to global scope.
|
|
54
63
|
|
|
55
|
-
1. Scan
|
|
64
|
+
1. Scan `~/.local/share/opencode/openhermes/plans/<project-name>-instincts.jsonl` for instincts with `confidence >= 0.85 AND applications >= 10`
|
|
56
65
|
2. Filter out project-specific patterns (reference paths, local APIs, domain terms)
|
|
57
66
|
3. Append filtered candidates to `%USERPROFILE%\.config\opencode\instincts.jsonl` (global)
|
|
58
67
|
4. Tag promoted instincts with `"promoted": true` in project file
|
|
@@ -4,13 +4,18 @@ description: "Full build loop: plan → build → verify → loop until done or
|
|
|
4
4
|
tier: 4
|
|
5
5
|
benefits-from: [oh-planner, oh-builder, oh-expert]
|
|
6
6
|
triggers:
|
|
7
|
-
- "
|
|
8
|
-
- "full build"
|
|
7
|
+
- "run the full build"
|
|
8
|
+
- "full build pipeline"
|
|
9
9
|
- "build loop"
|
|
10
10
|
- "build until done"
|
|
11
|
-
- "orchestrate"
|
|
12
|
-
- "pipeline"
|
|
11
|
+
- "orchestrate this build"
|
|
12
|
+
- "pipeline from plan"
|
|
13
13
|
- "run the plan"
|
|
14
|
+
- "manifest this"
|
|
15
|
+
route:
|
|
16
|
+
pass: oh-planner
|
|
17
|
+
fail: oh-expert
|
|
18
|
+
blocker: surface
|
|
14
19
|
---
|
|
15
20
|
|
|
16
21
|
# oh-manifest
|
|
@@ -31,21 +36,21 @@ Before any work begins, ALL of these MUST pass:
|
|
|
31
36
|
If any check fails → **STOP**. Report which check failed and why. Do not proceed to Phase 1 until the blocker is resolved.
|
|
32
37
|
|
|
33
38
|
### Step 1: Plan
|
|
34
|
-
- If
|
|
39
|
+
- If a plan file (`~/.local/share/opencode/openhermes/plans/<project-name>-plan-<nnn>.md`) exists, load and verify it is current
|
|
35
40
|
- If not, run `oh-planner` (Mode A, B, or C depending on context)
|
|
36
41
|
- Auto-decide minor scope decisions using decision principles
|
|
37
42
|
- Surface only: premises that need human judgment, or plan/alternative conflicts
|
|
38
43
|
|
|
39
44
|
### Step 2: Build
|
|
40
|
-
- For each phase in plan
|
|
45
|
+
- For each phase in the plan file, run `oh-builder` (Mode D: From Plan)
|
|
41
46
|
- Implements phases in dependency order
|
|
42
47
|
- Parallelizable phases may be delegated to sub-agents
|
|
43
48
|
- Auto-decide implementation choices using decision principles
|
|
44
49
|
|
|
45
50
|
### Step 3: Verify
|
|
46
|
-
- Check each phase against its verification criteria in plan
|
|
51
|
+
- Check each phase against its verification criteria in the plan file
|
|
47
52
|
- Run tests if they exist
|
|
48
|
-
- If phase passes: mark complete in plan
|
|
53
|
+
- If phase passes: mark complete in plan file, proceed to next
|
|
49
54
|
- If phase fails: diagnose (use oh-expert self-diagnosis), fix, re-verify
|
|
50
55
|
- If fix is impossible within scope: surface blocker
|
|
51
56
|
|
|
@@ -110,8 +115,8 @@ When a blocker is encountered:
|
|
|
110
115
|
- Auto-deciding premises (fundamental assumptions need user input)
|
|
111
116
|
- Pushing through blockers (surface immediately, don't try 5 workarounds silently)
|
|
112
117
|
- Skipping verification (verify every phase, not just the final result)
|
|
113
|
-
- Parallelizing dependent phases (respect the dependency order in plan
|
|
114
|
-
- Forgetting to update plan
|
|
118
|
+
- Parallelizing dependent phases (respect the dependency order in the plan file)
|
|
119
|
+
- Forgetting to update the plan file with completion status
|
|
115
120
|
- Ignoring escalation triggers (stall means pause, not try harder)
|
|
116
121
|
|
|
117
122
|
## Routing
|
|
@@ -4,15 +4,22 @@ description: "Multi-lens plan review: 4 perspectives in one skill. Choose Engine
|
|
|
4
4
|
tier: 3
|
|
5
5
|
benefits-from: [oh-planner, oh-expert]
|
|
6
6
|
triggers:
|
|
7
|
-
- "plan
|
|
8
|
-
- "review the plan"
|
|
9
|
-
- "architecture review"
|
|
10
|
-
- "design review"
|
|
11
|
-
- "ux review"
|
|
12
|
-
- "dx review"
|
|
7
|
+
- "review this plan"
|
|
8
|
+
- "review the plan file"
|
|
9
|
+
- "architecture review of"
|
|
10
|
+
- "design review the plan"
|
|
11
|
+
- "ux review this plan"
|
|
12
|
+
- "dx review the plan"
|
|
13
13
|
- "strategy review"
|
|
14
|
-
- "
|
|
14
|
+
- "engineering review"
|
|
15
15
|
- "ceo review"
|
|
16
|
+
- "review plan from"
|
|
17
|
+
route:
|
|
18
|
+
pass:
|
|
19
|
+
- oh-grill
|
|
20
|
+
- oh-manifest
|
|
21
|
+
fail: oh-planner
|
|
22
|
+
blocker: surface
|
|
16
23
|
---
|
|
17
24
|
|
|
18
25
|
# oh-plan-review
|
|
@@ -110,7 +117,7 @@ Product/CEO review with 4 scope modes.
|
|
|
110
117
|
|
|
111
118
|
## Output
|
|
112
119
|
|
|
113
|
-
After each lens, the plan file (
|
|
120
|
+
After each lens, the plan file (`~/.local/share/opencode/openhermes/plans/<project-name>-plan-<nnn>.md`) is updated with findings and decisions. The user reviews and accepts changes interactively.
|
|
114
121
|
|
|
115
122
|
## Rules
|
|
116
123
|
|
|
@@ -6,17 +6,23 @@ benefits-from: [oh-expert, oh-grill]
|
|
|
6
6
|
triggers:
|
|
7
7
|
- "plan this"
|
|
8
8
|
- "how should I build"
|
|
9
|
-
- "architecture"
|
|
9
|
+
- "plan the architecture for"
|
|
10
10
|
- "design this feature"
|
|
11
11
|
- "brainstorm"
|
|
12
12
|
- "autoplan"
|
|
13
|
-
- "strategy"
|
|
14
|
-
- "scope this"
|
|
13
|
+
- "strategy for this feature"
|
|
14
|
+
- "scope this feature"
|
|
15
|
+
- "create a plan for"
|
|
16
|
+
- "whats the plan for"
|
|
17
|
+
route:
|
|
18
|
+
pass: oh-grill
|
|
19
|
+
fail: oh-planner
|
|
20
|
+
blocker: surface
|
|
15
21
|
---
|
|
16
22
|
|
|
17
23
|
# oh-planner
|
|
18
24
|
|
|
19
|
-
The ALL-arounder planner. Merges brainstorm, architecture analysis, strategy review, and automatic plan review into one skill. Produces
|
|
25
|
+
The ALL-arounder planner. Merges brainstorm, architecture analysis, strategy review, and automatic plan review into one skill. Produces a plan file in OpenCode's canonical storage at `~/.local/share/opencode/openhermes/plans/`.
|
|
20
26
|
|
|
21
27
|
## Entry Modes
|
|
22
28
|
|
|
@@ -80,9 +86,7 @@ Never auto-decide: premises (need human judgment) or cases where both the plan a
|
|
|
80
86
|
|
|
81
87
|
## Plan Artifact
|
|
82
88
|
|
|
83
|
-
Output goes in
|
|
84
|
-
|
|
85
|
-
**Then save a copy** to `%USERPROFILE%/.config/opencode/task/<project-name>-plan-<nnn>.md` (global, incrementing, persistent) per AGENTS.md persistent plan rules.
|
|
89
|
+
Output goes in `~/.local/share/opencode/openhermes/plans/<project-name>-plan-<nnn>.md` (OpenCode's canonical storage, sequence-numbered per-session/project). The plan ID matches the filename.
|
|
86
90
|
|
|
87
91
|
```markdown
|
|
88
92
|
# PLAN: <project-name>
|
|
@@ -93,7 +97,7 @@ Status: active
|
|
|
93
97
|
Created: <local-date-time>
|
|
94
98
|
Updated: <local-date-time>
|
|
95
99
|
Project Path: <absolute-project-path>
|
|
96
|
-
Plan Path:
|
|
100
|
+
Plan Path: ~/.local/share/opencode/openhermes/plans/<project-name>-plan-<nnn>.md
|
|
97
101
|
Objective: <short objective>
|
|
98
102
|
|
|
99
103
|
## Current State
|
|
@@ -123,6 +127,10 @@ Objective: <short objective>
|
|
|
123
127
|
|
|
124
128
|
- <what's done>
|
|
125
129
|
|
|
130
|
+
## Work Log
|
|
131
|
+
|
|
132
|
+
<timestamped entries for subagent handoffs>
|
|
133
|
+
|
|
126
134
|
## Blockers
|
|
127
135
|
|
|
128
136
|
- None
|
|
@@ -142,6 +150,8 @@ Objective: <short objective>
|
|
|
142
150
|
<anything else>
|
|
143
151
|
```
|
|
144
152
|
|
|
153
|
+
The plan file is self-contained — Tasks, Completed, Subagents, and Work Log sections eliminate the need for separate todo.md or work-log.md files.
|
|
154
|
+
|
|
145
155
|
## Anti-patterns
|
|
146
156
|
- Skipping strategy review for complex features (architecture mistakes compound)
|
|
147
157
|
- Plans at wrong granularity — too vague to execute or too detailed to read
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oh-prd
|
|
3
3
|
description: "Turn conversation context into a PRD and publish as GitHub issue"
|
|
4
|
+
tier: 2
|
|
5
|
+
triggers:
|
|
6
|
+
- "write a prd"
|
|
7
|
+
- "product requirements"
|
|
8
|
+
- "prd for"
|
|
9
|
+
route:
|
|
10
|
+
pass: oh-issue
|
|
11
|
+
fail: oh-grill
|
|
12
|
+
blocker: surface
|
|
4
13
|
---
|
|
5
14
|
|
|
6
15
|
# oh-prd
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oh-refactor
|
|
3
|
+
description: "Surgical, behavior-preserving code refactoring. Extract functions, eliminate duplication, improve type safety, remove dead code, simplify conditionals. Use when code is hard to maintain, functions are too long, code smells accumulate, or user asks to clean up/improve/refactor code."
|
|
4
|
+
tier: 3
|
|
5
|
+
benefits-from: [oh-investigate, oh-review]
|
|
6
|
+
triggers:
|
|
7
|
+
- "refactor"
|
|
8
|
+
- "clean up"
|
|
9
|
+
- "improve this code"
|
|
10
|
+
- "code smell"
|
|
11
|
+
- "make this better"
|
|
12
|
+
- "extract method"
|
|
13
|
+
- "reduce duplication"
|
|
14
|
+
- "fix this mess"
|
|
15
|
+
- "technical debt"
|
|
16
|
+
- "god function"
|
|
17
|
+
- "long method"
|
|
18
|
+
- "nested conditionals"
|
|
19
|
+
route:
|
|
20
|
+
pass: oh-gauntlet
|
|
21
|
+
fail: oh-planner
|
|
22
|
+
blocker: surface
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# oh-refactor
|
|
26
|
+
|
|
27
|
+
Improve code structure and readability **without changing external behavior**. Refactoring is gradual evolution, not revolution.
|
|
28
|
+
|
|
29
|
+
## When to Use
|
|
30
|
+
|
|
31
|
+
- Code is hard to understand or maintain
|
|
32
|
+
- Functions/classes are too large
|
|
33
|
+
- Code smells need addressing
|
|
34
|
+
- Adding features is difficult due to code structure
|
|
35
|
+
- User asks "clean up this code", "refactor this", "improve this"
|
|
36
|
+
- Technical debt has accumulated to the point it slows development
|
|
37
|
+
|
|
38
|
+
## Refactoring Principles
|
|
39
|
+
|
|
40
|
+
### Golden Rules
|
|
41
|
+
|
|
42
|
+
1. **Behavior is preserved** — Refactoring doesn't change what the code does, only how. If the goal changes behavior, that's a feature, not a refactor.
|
|
43
|
+
2. **Small steps** — Make one change, verify, commit, repeat. Never batch refactoring changes.
|
|
44
|
+
3. **Tests are essential** — Without a fast, reliable test, you're not refactoring, you're editing blind. Write tests first if they don't exist.
|
|
45
|
+
4. **One thing at a time** — Never mix refactoring with feature changes in the same commit.
|
|
46
|
+
5. **Commit between safe states** — Commit before starting, commit after each green test run.
|
|
47
|
+
|
|
48
|
+
### When NOT to Refactor
|
|
49
|
+
|
|
50
|
+
- Code that works and will never change again
|
|
51
|
+
- Critical production code without tests (add tests first)
|
|
52
|
+
- Under a tight deadline with no test safety net
|
|
53
|
+
- "Just because" — every refactor needs a clear purpose
|
|
54
|
+
|
|
55
|
+
## Workflow
|
|
56
|
+
|
|
57
|
+
### Phase 1: Prepare
|
|
58
|
+
|
|
59
|
+
1. **Check test coverage** — run existing tests. If coverage is thin, write characterization tests that lock down current behavior before touching anything.
|
|
60
|
+
2. **Commit current state** — `git commit` so you can diff and revert cleanly.
|
|
61
|
+
3. **Create a feature branch** — isolate refactoring work from other changes.
|
|
62
|
+
|
|
63
|
+
### Phase 2: Identify
|
|
64
|
+
|
|
65
|
+
1. Find the code smell to address (see [Code Smells](#common-code-smells--fixes) below).
|
|
66
|
+
2. Understand what the code actually does — trace all code paths.
|
|
67
|
+
3. Plan the smallest refactoring that makes the problem better.
|
|
68
|
+
4. If behavior is unclear, delegate to `oh-investigate` before refactoring.
|
|
69
|
+
|
|
70
|
+
### Phase 3: Refactor (small steps)
|
|
71
|
+
|
|
72
|
+
1. Make one small change.
|
|
73
|
+
2. Run tests.
|
|
74
|
+
3. Commit if tests pass.
|
|
75
|
+
4. Repeat until the smell is gone.
|
|
76
|
+
|
|
77
|
+
### Phase 4: Verify
|
|
78
|
+
|
|
79
|
+
1. All tests pass.
|
|
80
|
+
2. Manual smoke test if full coverage is missing.
|
|
81
|
+
3. Performance unchanged or improved.
|
|
82
|
+
4. Diff shows only structural changes — no logic changes.
|
|
83
|
+
|
|
84
|
+
### Phase 5: Clean Up
|
|
85
|
+
|
|
86
|
+
1. Remove commented-out code, stale imports, dead paths.
|
|
87
|
+
2. Update inline docs only if behavior semantics changed.
|
|
88
|
+
3. Final commit.
|
|
89
|
+
4. Optionally route to `oh-review` for post-refactor quality gate.
|
|
90
|
+
|
|
91
|
+
## Common Code Smells & Fixes
|
|
92
|
+
|
|
93
|
+
### 1. Long Method/Function
|
|
94
|
+
|
|
95
|
+
```diff
|
|
96
|
+
# BAD: 200-line function that does everything
|
|
97
|
+
- async function processOrder(orderId) {
|
|
98
|
+
- // 50 lines: fetch order
|
|
99
|
+
- // 30 lines: validate order
|
|
100
|
+
- // 40 lines: calculate pricing
|
|
101
|
+
- // 30 lines: update inventory
|
|
102
|
+
- // 20 lines: create shipment
|
|
103
|
+
- // 30 lines: send notifications
|
|
104
|
+
- }
|
|
105
|
+
|
|
106
|
+
# GOOD: Broken into focused functions
|
|
107
|
+
+ async function processOrder(orderId) {
|
|
108
|
+
+ const order = await fetchOrder(orderId);
|
|
109
|
+
+ validateOrder(order);
|
|
110
|
+
+ const pricing = calculatePricing(order);
|
|
111
|
+
+ await updateInventory(order);
|
|
112
|
+
+ const shipment = await createShipment(order);
|
|
113
|
+
+ await sendNotifications(order, pricing, shipment);
|
|
114
|
+
+ return { order, pricing, shipment };
|
|
115
|
+
+ }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 2. Duplicated Code
|
|
119
|
+
|
|
120
|
+
```diff
|
|
121
|
+
# BAD: Same logic in multiple places
|
|
122
|
+
- function calculateUserDiscount(user) {
|
|
123
|
+
- if (user.membership === 'gold') return user.total * 0.2;
|
|
124
|
+
- if (user.membership === 'silver') return user.total * 0.1;
|
|
125
|
+
- return 0;
|
|
126
|
+
- }
|
|
127
|
+
- function calculateOrderDiscount(order) {
|
|
128
|
+
- if (order.user.membership === 'gold') return order.total * 0.2;
|
|
129
|
+
- if (order.user.membership === 'silver') return order.total * 0.1;
|
|
130
|
+
- return 0;
|
|
131
|
+
- }
|
|
132
|
+
|
|
133
|
+
# GOOD: Extract common logic
|
|
134
|
+
+ function getMembershipDiscountRate(membership) {
|
|
135
|
+
+ const rates = { gold: 0.2, silver: 0.1 };
|
|
136
|
+
+ return rates[membership] || 0;
|
|
137
|
+
+ }
|
|
138
|
+
+ function calculateUserDiscount(user) {
|
|
139
|
+
+ return user.total * getMembershipDiscountRate(user.membership);
|
|
140
|
+
+ }
|
|
141
|
+
+ function calculateOrderDiscount(order) {
|
|
142
|
+
+ return order.total * getMembershipDiscountRate(order.user.membership);
|
|
143
|
+
+ }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 3. Large Class/Module (God Object)
|
|
147
|
+
|
|
148
|
+
```diff
|
|
149
|
+
# BAD: God object that knows too much
|
|
150
|
+
- class UserManager {
|
|
151
|
+
- createUser() { /* ... */ }
|
|
152
|
+
- updateUser() { /* ... */ }
|
|
153
|
+
- deleteUser() { /* ... */ }
|
|
154
|
+
- sendEmail() { /* ... */ }
|
|
155
|
+
- generateReport() { /* ... */ }
|
|
156
|
+
- handlePayment() { /* ... */ }
|
|
157
|
+
- validateAddress() { /* ... */ }
|
|
158
|
+
- }
|
|
159
|
+
|
|
160
|
+
# GOOD: Single responsibility per class
|
|
161
|
+
+ class UserService {
|
|
162
|
+
+ create(data) { /* ... */ }
|
|
163
|
+
+ update(id, data) { /* ... */ }
|
|
164
|
+
+ delete(id) { /* ... */ }
|
|
165
|
+
+ }
|
|
166
|
+
+ class EmailService {
|
|
167
|
+
+ send(to, subject, body) { /* ... */ }
|
|
168
|
+
+ }
|
|
169
|
+
+ class ReportService {
|
|
170
|
+
+ generate(type, params) { /* ... */ }
|
|
171
|
+
+ }
|
|
172
|
+
+ class PaymentService {
|
|
173
|
+
+ process(amount, method) { /* ... */ }
|
|
174
|
+
+ }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 4. Long Parameter List
|
|
178
|
+
|
|
179
|
+
```diff
|
|
180
|
+
# BAD: Too many parameters
|
|
181
|
+
- function createUser(email, password, name, age, address, city, country, phone) { }
|
|
182
|
+
|
|
183
|
+
# GOOD: Group related parameters
|
|
184
|
+
+ interface UserData {
|
|
185
|
+
+ email: string;
|
|
186
|
+
+ password: string;
|
|
187
|
+
+ name: string;
|
|
188
|
+
+ age?: number;
|
|
189
|
+
+ address?: Address;
|
|
190
|
+
+ phone?: string;
|
|
191
|
+
+ }
|
|
192
|
+
+ function createUser(data: UserData) { }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 5. Feature Envy
|
|
196
|
+
|
|
197
|
+
```diff
|
|
198
|
+
# BAD: Method uses another object's data more than its own
|
|
199
|
+
- class Order {
|
|
200
|
+
- calculateDiscount(user) {
|
|
201
|
+
- if (user.membershipLevel === 'gold') return this.total * 0.2;
|
|
202
|
+
- if (user.accountAge > 365) return this.total * 0.1;
|
|
203
|
+
- return 0;
|
|
204
|
+
- }
|
|
205
|
+
- }
|
|
206
|
+
|
|
207
|
+
# GOOD: Move logic to the object that owns the data
|
|
208
|
+
+ class User {
|
|
209
|
+
+ getDiscountRate(orderTotal) {
|
|
210
|
+
+ if (this.membershipLevel === 'gold') return 0.2;
|
|
211
|
+
+ if (this.accountAge > 365) return 0.1;
|
|
212
|
+
+ return 0;
|
|
213
|
+
+ }
|
|
214
|
+
+ }
|
|
215
|
+
+ class Order {
|
|
216
|
+
+ calculateDiscount(user) {
|
|
217
|
+
+ return this.total * user.getDiscountRate(this.total);
|
|
218
|
+
+ }
|
|
219
|
+
+ }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### 6. Primitive Obsession
|
|
223
|
+
|
|
224
|
+
```diff
|
|
225
|
+
# BAD: Using primitives for domain concepts
|
|
226
|
+
- function sendEmail(to, subject, body) { }
|
|
227
|
+
- sendEmail('user@example.com', 'Hello', '...');
|
|
228
|
+
|
|
229
|
+
# GOOD: Use domain types
|
|
230
|
+
+ class Email {
|
|
231
|
+
+ private constructor(public readonly value: string) {
|
|
232
|
+
+ if (!Email.isValid(value)) throw new Error('Invalid email');
|
|
233
|
+
+ }
|
|
234
|
+
+ static create(value: string) { return new Email(value); }
|
|
235
|
+
+ static isValid(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }
|
|
236
|
+
+ }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 7. Magic Numbers/Strings
|
|
240
|
+
|
|
241
|
+
```diff
|
|
242
|
+
# BAD: Unexplained values
|
|
243
|
+
- if (user.status === 2) { }
|
|
244
|
+
- const discount = total * 0.15;
|
|
245
|
+
|
|
246
|
+
# GOOD: Named constants
|
|
247
|
+
+ const UserStatus = { ACTIVE: 1, INACTIVE: 2, SUSPENDED: 3 } as const;
|
|
248
|
+
+ const DISCOUNT_RATES = { STANDARD: 0.1, PREMIUM: 0.15, VIP: 0.2 } as const;
|
|
249
|
+
+ if (user.status === UserStatus.INACTIVE) { }
|
|
250
|
+
+ const discount = total * DISCOUNT_RATES.PREMIUM;
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 8. Nested Conditionals (Arrow Code)
|
|
254
|
+
|
|
255
|
+
```diff
|
|
256
|
+
# BAD: Arrow code
|
|
257
|
+
- function process(order) {
|
|
258
|
+
- if (order) {
|
|
259
|
+
- if (order.user) {
|
|
260
|
+
- if (order.user.isActive) {
|
|
261
|
+
- if (order.total > 0) {
|
|
262
|
+
- return processOrder(order);
|
|
263
|
+
- } else { return { error: 'Invalid total' }; }
|
|
264
|
+
- } else { return { error: 'User inactive' }; }
|
|
265
|
+
- } else { return { error: 'No user' }; }
|
|
266
|
+
- } else { return { error: 'No order' }; }
|
|
267
|
+
- }
|
|
268
|
+
|
|
269
|
+
# GOOD: Guard clauses / early returns
|
|
270
|
+
+ function process(order) {
|
|
271
|
+
+ if (!order) return { error: 'No order' };
|
|
272
|
+
+ if (!order.user) return { error: 'No user' };
|
|
273
|
+
+ if (!order.user.isActive) return { error: 'User inactive' };
|
|
274
|
+
+ if (order.total <= 0) return { error: 'Invalid total' };
|
|
275
|
+
+ return processOrder(order);
|
|
276
|
+
+ }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 9. Dead Code
|
|
280
|
+
|
|
281
|
+
```diff
|
|
282
|
+
# BAD: Unused code lingers
|
|
283
|
+
- function oldImplementation() { }
|
|
284
|
+
- const DEPRECATED_VALUE = 5;
|
|
285
|
+
- import { unusedThing } from './somewhere';
|
|
286
|
+
|
|
287
|
+
# GOOD: Remove it
|
|
288
|
+
+ // Delete unused functions, imports, commented-out code
|
|
289
|
+
+ // Git history has everything you need
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 10. Inappropriate Intimacy
|
|
293
|
+
|
|
294
|
+
```diff
|
|
295
|
+
# BAD: One class reaches deep into another
|
|
296
|
+
- class OrderProcessor {
|
|
297
|
+
- process(order) {
|
|
298
|
+
- order.user.profile.address.street; // Too intimate
|
|
299
|
+
- }
|
|
300
|
+
- }
|
|
301
|
+
|
|
302
|
+
# GOOD: Ask, don't tell
|
|
303
|
+
+ class OrderProcessor {
|
|
304
|
+
+ process(order) {
|
|
305
|
+
+ order.getShippingAddress(); // Order knows how to get it
|
|
306
|
+
+ order.save();
|
|
307
|
+
+ }
|
|
308
|
+
+ }
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Extract Method Refactoring
|
|
312
|
+
|
|
313
|
+
```diff
|
|
314
|
+
# Before: One long function
|
|
315
|
+
- function printReport(users) {
|
|
316
|
+
- console.log('USER REPORT');
|
|
317
|
+
- console.log('============');
|
|
318
|
+
- console.log(`Total users: ${users.length}`);
|
|
319
|
+
- console.log('ACTIVE USERS');
|
|
320
|
+
- const active = users.filter(u => u.isActive);
|
|
321
|
+
- active.forEach(u => console.log(`- ${u.name} (${u.email})`));
|
|
322
|
+
- console.log(`Active: ${active.length}`);
|
|
323
|
+
- console.log('INACTIVE USERS');
|
|
324
|
+
- const inactive = users.filter(u => !u.isActive);
|
|
325
|
+
- inactive.forEach(u => console.log(`- ${u.name} (${u.email})`));
|
|
326
|
+
- console.log(`Inactive: ${inactive.length}`);
|
|
327
|
+
- }
|
|
328
|
+
|
|
329
|
+
# After: Extracted methods
|
|
330
|
+
+ function printReport(users) {
|
|
331
|
+
+ printHeader('USER REPORT');
|
|
332
|
+
+ console.log(`Total users: ${users.length}\n`);
|
|
333
|
+
+ printUserSection('ACTIVE USERS', users.filter(u => u.isActive));
|
|
334
|
+
+ printUserSection('INACTIVE USERS', users.filter(u => !u.isActive));
|
|
335
|
+
+ }
|
|
336
|
+
+ function printHeader(title) {
|
|
337
|
+
+ const line = '='.repeat(title.length);
|
|
338
|
+
+ console.log(title); console.log(line); console.log('');
|
|
339
|
+
+ }
|
|
340
|
+
+ function printUserSection(title, users) {
|
|
341
|
+
+ console.log(title);
|
|
342
|
+
+ console.log('-'.repeat(title.length));
|
|
343
|
+
+ users.forEach(u => console.log(`- ${u.name} (${u.email})`));
|
|
344
|
+
+ console.log(`${title.split(' ')[0]}: ${users.length}`);
|
|
345
|
+
+ }
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Design Patterns for Refactoring
|
|
349
|
+
|
|
350
|
+
### Strategy Pattern
|
|
351
|
+
|
|
352
|
+
Replace conditional branching with composable strategies:
|
|
353
|
+
|
|
354
|
+
```diff
|
|
355
|
+
- function calculateShipping(order, method) {
|
|
356
|
+
- if (method === 'standard') return order.total > 50 ? 0 : 5.99;
|
|
357
|
+
- else if (method === 'express') return order.total > 100 ? 9.99 : 14.99;
|
|
358
|
+
- else if (method === 'overnight') return 29.99;
|
|
359
|
+
- }
|
|
360
|
+
|
|
361
|
+
+ interface ShippingStrategy { calculate(order: Order): number; }
|
|
362
|
+
+ class StandardShipping implements ShippingStrategy {
|
|
363
|
+
+ calculate(order: Order) { return order.total > 50 ? 0 : 5.99; }
|
|
364
|
+
+ }
|
|
365
|
+
+ class ExpressShipping implements ShippingStrategy {
|
|
366
|
+
+ calculate(order: Order) { return order.total > 100 ? 9.99 : 14.99; }
|
|
367
|
+
+ }
|
|
368
|
+
+ function calculateShipping(order: Order, strategy: ShippingStrategy) {
|
|
369
|
+
+ return strategy.calculate(order);
|
|
370
|
+
+ }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Guard Clauses
|
|
374
|
+
|
|
375
|
+
Replace nested conditions with early returns. This is the single highest-ROI refactoring pattern — it flattens deeply nested code immediately.
|
|
376
|
+
|
|
377
|
+
## Common Refactoring Operations
|
|
378
|
+
|
|
379
|
+
| Operation | Description |
|
|
380
|
+
|---|---|
|
|
381
|
+
| Extract Method | Turn code fragment into named function |
|
|
382
|
+
| Extract Class | Move related behavior to new class |
|
|
383
|
+
| Inline Method | Move method body back to single caller |
|
|
384
|
+
| Rename Method/Variable | Improve clarity |
|
|
385
|
+
| Introduce Parameter Object | Group related parameters |
|
|
386
|
+
| Replace Conditional with Polymorphism | Dispatch by type instead of if/switch |
|
|
387
|
+
| Replace Magic Number with Constant | Named constants for literals |
|
|
388
|
+
| Decompose Conditional | Break complex conditions into named predicates |
|
|
389
|
+
| Consolidate Conditional | Combine duplicate conditions |
|
|
390
|
+
| Replace Nested Conditional with Guard Clauses | Early returns |
|
|
391
|
+
| Replace Inheritance with Delegation | Composition over inheritance |
|
|
392
|
+
|
|
393
|
+
## Refactoring Checklist
|
|
394
|
+
|
|
395
|
+
### Code Quality
|
|
396
|
+
- [ ] Functions are small (< 50 lines)
|
|
397
|
+
- [ ] Functions do one thing
|
|
398
|
+
- [ ] No duplicated code
|
|
399
|
+
- [ ] Descriptive names (variables, functions, classes)
|
|
400
|
+
- [ ] No magic numbers/strings
|
|
401
|
+
- [ ] Dead code removed
|
|
402
|
+
|
|
403
|
+
### Structure
|
|
404
|
+
- [ ] Related code is together
|
|
405
|
+
- [ ] Clear module boundaries
|
|
406
|
+
- [ ] Dependencies flow in one direction
|
|
407
|
+
- [ ] No circular dependencies
|
|
408
|
+
|
|
409
|
+
### Type Safety
|
|
410
|
+
- [ ] Types defined for all public APIs
|
|
411
|
+
- [ ] No `any` types without justification
|
|
412
|
+
- [ ] Nullable types explicitly marked
|
|
413
|
+
|
|
414
|
+
### Testing
|
|
415
|
+
- [ ] Refactored code is tested
|
|
416
|
+
- [ ] Tests cover edge cases
|
|
417
|
+
- [ ] All tests pass
|
|
418
|
+
|
|
419
|
+
## Routing
|
|
420
|
+
|
|
421
|
+
| Outcome | Route |
|
|
422
|
+
|---|---|
|
|
423
|
+
| pass | -> oh-review (post-refactor quality gate) |
|
|
424
|
+
| behavior unclear | -> oh-investigate (diagnose before refactoring) |
|
|
425
|
+
| test gap found | -> oh-builder TDD mode (add characterization tests first) |
|
|
426
|
+
| blocker | -> surface to user |
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oh-retro
|
|
3
3
|
description: "Weekly engineering retrospective — analyze commit history and work patterns"
|
|
4
|
+
tier: 3
|
|
5
|
+
triggers:
|
|
6
|
+
- "retrospective"
|
|
7
|
+
- "retro for"
|
|
8
|
+
- "post-ship review"
|
|
9
|
+
route:
|
|
10
|
+
pass: oh-planner
|
|
11
|
+
fail: oh-handoff
|
|
12
|
+
blocker: surface
|
|
4
13
|
---
|
|
5
14
|
|
|
6
15
|
# oh-retro
|