@harnspec/cli 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/README.md +225 -0
- package/bin/harnspec-rust.js +201 -0
- package/bin/harnspec.js +10 -0
- package/binaries/darwin-arm64/harnspec +0 -0
- package/binaries/darwin-arm64/harnspec-http +0 -0
- package/binaries/darwin-arm64/package.json +24 -0
- package/binaries/darwin-arm64/postinstall.js +17 -0
- package/binaries/darwin-x64/harnspec +0 -0
- package/binaries/darwin-x64/harnspec-http +0 -0
- package/binaries/darwin-x64/package.json +24 -0
- package/binaries/darwin-x64/postinstall.js +17 -0
- package/binaries/linux-x64/harnspec +0 -0
- package/binaries/linux-x64/harnspec-http +0 -0
- package/binaries/linux-x64/package.json +24 -0
- package/binaries/linux-x64/postinstall.js +17 -0
- package/binaries/windows-x64/harnspec-http.exe +0 -0
- package/binaries/windows-x64/harnspec.exe +0 -0
- package/binaries/windows-x64/package.json +24 -0
- package/binaries/windows-x64/postinstall.js +6 -0
- package/package.json +51 -0
- package/templates/detailed/AGENTS-minimal.md +9 -0
- package/templates/detailed/AGENTS.md +118 -0
- package/templates/detailed/README.md +28 -0
- package/templates/detailed/config.json +20 -0
- package/templates/detailed/files/DESIGN.md +43 -0
- package/templates/detailed/files/PLAN.md +59 -0
- package/templates/detailed/files/README.md +30 -0
- package/templates/detailed/files/TEST.md +71 -0
- package/templates/examples/api-refactor/README.md +86 -0
- package/templates/examples/api-refactor/package.json +16 -0
- package/templates/examples/api-refactor/src/app.js +40 -0
- package/templates/examples/api-refactor/src/services/currencyService.js +43 -0
- package/templates/examples/api-refactor/src/services/timezoneService.js +41 -0
- package/templates/examples/api-refactor/src/services/weatherService.js +42 -0
- package/templates/examples/dark-theme/README.md +69 -0
- package/templates/examples/dark-theme/package.json +16 -0
- package/templates/examples/dark-theme/src/public/app.js +277 -0
- package/templates/examples/dark-theme/src/public/index.html +225 -0
- package/templates/examples/dark-theme/src/public/style.css +625 -0
- package/templates/examples/dark-theme/src/server.js +18 -0
- package/templates/examples/dashboard-widgets/README.md +74 -0
- package/templates/examples/dashboard-widgets/index.html +12 -0
- package/templates/examples/dashboard-widgets/package.json +22 -0
- package/templates/examples/dashboard-widgets/src/App.css +20 -0
- package/templates/examples/dashboard-widgets/src/App.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.css +17 -0
- package/templates/examples/dashboard-widgets/src/components/Dashboard.jsx +15 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.css +23 -0
- package/templates/examples/dashboard-widgets/src/components/WidgetWrapper.jsx +16 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.css +33 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/ChartWidget.jsx +28 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.css +24 -0
- package/templates/examples/dashboard-widgets/src/components/widgets/StatsWidget.jsx +22 -0
- package/templates/examples/dashboard-widgets/src/index.css +13 -0
- package/templates/examples/dashboard-widgets/src/main.jsx +10 -0
- package/templates/examples/dashboard-widgets/src/utils/mockData.js +30 -0
- package/templates/examples/dashboard-widgets/vite.config.js +6 -0
- package/templates/standard/AGENTS-minimal.md +10 -0
- package/templates/standard/AGENTS.md +118 -0
- package/templates/standard/README.md +25 -0
- package/templates/standard/config.json +18 -0
- package/templates/standard/files/README.md +42 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# AI Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Project: {project_name}
|
|
4
|
+
|
|
5
|
+
## 🚨 CRITICAL: Before ANY Task
|
|
6
|
+
|
|
7
|
+
**STOP and check these first:**
|
|
8
|
+
|
|
9
|
+
1. **Discover context** → Use `board` tool to see project state
|
|
10
|
+
2. **Search for related work** → Use `search` tool before creating new specs
|
|
11
|
+
3. **Never create files manually** → Always use `create` tool for new specs
|
|
12
|
+
|
|
13
|
+
> **Why?** Skipping discovery creates duplicate work. Manual file creation breaks HarnSpec tooling.
|
|
14
|
+
|
|
15
|
+
## 🔧 Managing Specs
|
|
16
|
+
|
|
17
|
+
### CLI Commands with Agent Skills
|
|
18
|
+
|
|
19
|
+
| Action | Command |
|
|
20
|
+
|--------|---------|
|
|
21
|
+
| Project status | `harnspec board` |
|
|
22
|
+
| List specs | `harnspec list` |
|
|
23
|
+
| Search specs | `harnspec search "query"` |
|
|
24
|
+
| View spec | `harnspec view <spec>` |
|
|
25
|
+
| Create spec | `harnspec create <name>` |
|
|
26
|
+
| Update spec | `harnspec update <spec> --status <status>` |
|
|
27
|
+
| Link specs | `harnspec rel add <spec> --depends-on <other>` |
|
|
28
|
+
| Unlink specs | `harnspec rel rm <spec> --depends-on <other>` |
|
|
29
|
+
| Dependencies | `harnspec deps <spec>` |
|
|
30
|
+
| Token count | `harnspec tokens <spec>` |
|
|
31
|
+
| Validate specs | `harnspec validate` |
|
|
32
|
+
|
|
33
|
+
## ⚠️ Core Rules
|
|
34
|
+
|
|
35
|
+
| Rule | Details |
|
|
36
|
+
|------|---------|
|
|
37
|
+
| **NEVER edit frontmatter manually** | Use `update`, `link`, `unlink` for: `status`, `priority`, `tags`, `assignee`, `transitions`, timestamps, `depends_on` |
|
|
38
|
+
| **ALWAYS link spec references** | Content mentions another spec → `harnspec link <spec> --depends-on <other>` |
|
|
39
|
+
| **Track status transitions** | `planned` → `in-progress` (before coding) → `complete` (after done) |
|
|
40
|
+
| **Keep specs current** | Document progress, decisions, and learnings as work happens. Obsolete specs mislead both humans and AI |
|
|
41
|
+
| **No nested code blocks** | Use indentation instead |
|
|
42
|
+
|
|
43
|
+
### 🚫 Common Mistakes
|
|
44
|
+
|
|
45
|
+
| ❌ Don't | ✅ Do Instead |
|
|
46
|
+
|----------|---------------|
|
|
47
|
+
| Create spec files manually | Use `create` tool |
|
|
48
|
+
| Skip discovery | Run `board` and `search` first |
|
|
49
|
+
| Leave status as "planned" | Update to `in-progress` before coding |
|
|
50
|
+
| Edit frontmatter manually | Use `update` tool |
|
|
51
|
+
| Complete spec without documentation | Document progress, prompts, learnings first |
|
|
52
|
+
|
|
53
|
+
## 📋 SDD Workflow
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
BEFORE: board → search → check existing specs
|
|
57
|
+
DURING: update status to in-progress → code → document decisions → link dependencies
|
|
58
|
+
AFTER: document completion → update status to complete
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Status tracks implementation, NOT spec writing.**
|
|
62
|
+
|
|
63
|
+
## Spec Dependencies
|
|
64
|
+
|
|
65
|
+
Use `depends_on` to express blocking relationships between specs:
|
|
66
|
+
|
|
67
|
+
- **`depends_on`** = True blocker, work order matters, directional (A depends on B)
|
|
68
|
+
|
|
69
|
+
Link dependencies when one spec builds on another:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
harnspec link <spec> --depends-on <other-spec>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## When to Use Specs
|
|
76
|
+
|
|
77
|
+
| ✅ Write spec | ❌ Skip spec |
|
|
78
|
+
|---------------|--------------|
|
|
79
|
+
| Multi-part features | Bug fixes |
|
|
80
|
+
| Breaking changes | Trivial changes |
|
|
81
|
+
| Design decisions | Self-explanatory refactors |
|
|
82
|
+
|
|
83
|
+
## Token Thresholds
|
|
84
|
+
|
|
85
|
+
| Tokens | Status |
|
|
86
|
+
|--------|--------|
|
|
87
|
+
| <2,000 | ✅ Optimal |
|
|
88
|
+
| 2,000-3,500 | ✅ Good |
|
|
89
|
+
| 3,500-5,000 | ⚠️ Consider splitting |
|
|
90
|
+
| >5,000 | 🔴 Must split |
|
|
91
|
+
|
|
92
|
+
## Quality Validation
|
|
93
|
+
|
|
94
|
+
Before completing work, validate spec quality:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
harnspec validate # Check structure and quality
|
|
98
|
+
harnspec validate --check-deps # Verify dependency alignment
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Validation checks:
|
|
102
|
+
|
|
103
|
+
- Missing required sections
|
|
104
|
+
- Excessive length (>400 lines)
|
|
105
|
+
- Content/frontmatter dependency misalignment
|
|
106
|
+
- Invalid frontmatter fields
|
|
107
|
+
|
|
108
|
+
## First Principles (Priority Order)
|
|
109
|
+
|
|
110
|
+
1. **Context Economy** - <2,000 tokens optimal, >3,500 needs splitting
|
|
111
|
+
2. **Signal-to-Noise** - Every word must inform a decision
|
|
112
|
+
3. **Intent Over Implementation** - Capture why, let how emerge
|
|
113
|
+
4. **Bridge the Gap** - Both human and AI must understand
|
|
114
|
+
5. **Progressive Disclosure** - Add complexity only when pain is felt
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
**Remember:** HarnSpec tracks what you're building. Keep specs in sync with your work!
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Detailed Template
|
|
2
|
+
|
|
3
|
+
For complex specs that benefit from structured sub-specs. Demonstrates how to manage token limits.
|
|
4
|
+
|
|
5
|
+
## What's Included
|
|
6
|
+
|
|
7
|
+
- **AGENTS.md** - Same AI agent instructions as standard template
|
|
8
|
+
- **Sub-spec structure** - README.md + DESIGN.md + PLAN.md + TEST.md
|
|
9
|
+
- Demonstrates splitting specs to stay under token limits
|
|
10
|
+
- Example of real-world sub-spec organization
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Complex features with lots of detail
|
|
15
|
+
- Specs approaching 3,500+ tokens
|
|
16
|
+
- Need clear separation of concerns (design, plan, test)
|
|
17
|
+
- Large teams with multiple reviewers
|
|
18
|
+
|
|
19
|
+
## Philosophy
|
|
20
|
+
|
|
21
|
+
Keep it lean, but organized. Use sub-specs to manage complexity without overwhelming context. Each file stays focused and under token limits.
|
|
22
|
+
|
|
23
|
+
## Next Steps
|
|
24
|
+
|
|
25
|
+
You're ready to go! Ask your AI to create a spec for your next feature.
|
|
26
|
+
|
|
27
|
+
When a spec grows large, consider splitting sections into sub-spec files.
|
|
28
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Enterprise",
|
|
3
|
+
"description": "Governance-ready with approvals, compliance, and security",
|
|
4
|
+
"config": {
|
|
5
|
+
"template": "enterprise",
|
|
6
|
+
"specsDir": "specs",
|
|
7
|
+
"structure": {
|
|
8
|
+
"pattern": "flat",
|
|
9
|
+
"prefix": "",
|
|
10
|
+
"dateFormat": "YYYYMMDD",
|
|
11
|
+
"sequenceDigits": 3,
|
|
12
|
+
"defaultFile": "README.md"
|
|
13
|
+
},
|
|
14
|
+
"features": {
|
|
15
|
+
"aiAgents": true,
|
|
16
|
+
"compliance": true,
|
|
17
|
+
"approvals": true
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Design: {name}
|
|
2
|
+
|
|
3
|
+
> Part of [{name}](README.md)
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
<!-- High-level system design, components, interactions -->
|
|
8
|
+
|
|
9
|
+
## Technical Approach
|
|
10
|
+
|
|
11
|
+
<!-- Specific technologies, patterns, frameworks -->
|
|
12
|
+
|
|
13
|
+
## Design Decisions
|
|
14
|
+
|
|
15
|
+
<!-- Key decisions and rationale -->
|
|
16
|
+
|
|
17
|
+
### Decision 1
|
|
18
|
+
|
|
19
|
+
**Context**: <!-- What problem does this solve? -->
|
|
20
|
+
|
|
21
|
+
**Decision**: <!-- What did we choose? -->
|
|
22
|
+
|
|
23
|
+
**Rationale**: <!-- Why this approach? -->
|
|
24
|
+
|
|
25
|
+
**Trade-offs**: <!-- What are we giving up? -->
|
|
26
|
+
|
|
27
|
+
## Dependencies
|
|
28
|
+
|
|
29
|
+
<!-- What does this depend on? What depends on this? -->
|
|
30
|
+
|
|
31
|
+
### System Dependencies
|
|
32
|
+
-
|
|
33
|
+
|
|
34
|
+
### Team Dependencies
|
|
35
|
+
-
|
|
36
|
+
|
|
37
|
+
## Security & Compliance
|
|
38
|
+
|
|
39
|
+
<!-- Security implications, compliance requirements -->
|
|
40
|
+
|
|
41
|
+
- [ ] Handles sensitive data (PII, credentials, etc.)
|
|
42
|
+
- [ ] Security review completed
|
|
43
|
+
- [ ] Compliance requirements addressed
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Plan: {name}
|
|
2
|
+
|
|
3
|
+
> Part of [{name}](README.md)
|
|
4
|
+
|
|
5
|
+
## Implementation Phases
|
|
6
|
+
|
|
7
|
+
### Phase 1: [Name]
|
|
8
|
+
|
|
9
|
+
**Goal**: <!-- What does this phase achieve? -->
|
|
10
|
+
|
|
11
|
+
**Tasks**:
|
|
12
|
+
- [ ] Task 1
|
|
13
|
+
- [ ] Task 2
|
|
14
|
+
- [ ] Task 3
|
|
15
|
+
|
|
16
|
+
**Dependencies**: <!-- What must be done before this? -->
|
|
17
|
+
|
|
18
|
+
**Success criteria**: <!-- How do we know this is done? -->
|
|
19
|
+
|
|
20
|
+
### Phase 2: [Name]
|
|
21
|
+
|
|
22
|
+
**Goal**: <!-- What does this phase achieve? -->
|
|
23
|
+
|
|
24
|
+
**Tasks**:
|
|
25
|
+
- [ ] Task 1
|
|
26
|
+
- [ ] Task 2
|
|
27
|
+
- [ ] Task 3
|
|
28
|
+
|
|
29
|
+
**Dependencies**: <!-- What must be done before this? -->
|
|
30
|
+
|
|
31
|
+
**Success criteria**: <!-- How do we know this is done? -->
|
|
32
|
+
|
|
33
|
+
## Rollout Strategy
|
|
34
|
+
|
|
35
|
+
<!-- How will this be deployed to production? -->
|
|
36
|
+
|
|
37
|
+
**Staging**:
|
|
38
|
+
-
|
|
39
|
+
|
|
40
|
+
**Production**:
|
|
41
|
+
-
|
|
42
|
+
|
|
43
|
+
**Monitoring**:
|
|
44
|
+
-
|
|
45
|
+
|
|
46
|
+
**Rollback plan**:
|
|
47
|
+
-
|
|
48
|
+
|
|
49
|
+
## Timeline
|
|
50
|
+
|
|
51
|
+
| Phase | Duration | Dependencies |
|
|
52
|
+
|-------|----------|--------------|
|
|
53
|
+
| | | |
|
|
54
|
+
|
|
55
|
+
## Risks
|
|
56
|
+
|
|
57
|
+
| Risk | Impact | Mitigation |
|
|
58
|
+
|------|--------|------------|
|
|
59
|
+
| | | |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
status: planned
|
|
3
|
+
created: '{date}'
|
|
4
|
+
tags: []
|
|
5
|
+
priority: medium
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# {name}
|
|
9
|
+
|
|
10
|
+
> **Status**: {status} · **Priority**: {priority} · **Created**: {date}
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
<!-- What are we solving? Why now? Expected impact? -->
|
|
15
|
+
|
|
16
|
+
## Sub-Specs
|
|
17
|
+
|
|
18
|
+
For detailed information, see:
|
|
19
|
+
|
|
20
|
+
- **[DESIGN.md](DESIGN.md)** - Technical architecture and design decisions
|
|
21
|
+
- **[PLAN.md](PLAN.md)** - Implementation plan and phases
|
|
22
|
+
- **[TEST.md](TEST.md)** - Testing strategy and verification
|
|
23
|
+
|
|
24
|
+
## Quick Summary
|
|
25
|
+
|
|
26
|
+
<!-- Brief summary of the spec (2-3 paragraphs max) -->
|
|
27
|
+
|
|
28
|
+
## Notes
|
|
29
|
+
|
|
30
|
+
<!-- Key decisions, constraints, open questions -->
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Test: {name}
|
|
2
|
+
|
|
3
|
+
> Part of [{name}](README.md)
|
|
4
|
+
|
|
5
|
+
## Testing Strategy
|
|
6
|
+
|
|
7
|
+
<!-- Overall approach to verifying this works -->
|
|
8
|
+
|
|
9
|
+
## Unit Tests
|
|
10
|
+
|
|
11
|
+
<!-- Component-level testing -->
|
|
12
|
+
|
|
13
|
+
**Scope**:
|
|
14
|
+
-
|
|
15
|
+
|
|
16
|
+
**Key test cases**:
|
|
17
|
+
- [ ] Test case 1
|
|
18
|
+
- [ ] Test case 2
|
|
19
|
+
- [ ] Test case 3
|
|
20
|
+
|
|
21
|
+
## Integration Tests
|
|
22
|
+
|
|
23
|
+
<!-- System interaction testing -->
|
|
24
|
+
|
|
25
|
+
**Scope**:
|
|
26
|
+
-
|
|
27
|
+
|
|
28
|
+
**Key test cases**:
|
|
29
|
+
- [ ] Test case 1
|
|
30
|
+
- [ ] Test case 2
|
|
31
|
+
- [ ] Test case 3
|
|
32
|
+
|
|
33
|
+
## Performance Tests
|
|
34
|
+
|
|
35
|
+
<!-- Load, stress, and performance testing -->
|
|
36
|
+
|
|
37
|
+
**Requirements**:
|
|
38
|
+
-
|
|
39
|
+
|
|
40
|
+
**Test scenarios**:
|
|
41
|
+
- [ ] Scenario 1
|
|
42
|
+
- [ ] Scenario 2
|
|
43
|
+
|
|
44
|
+
## Security Tests
|
|
45
|
+
|
|
46
|
+
<!-- Security validation -->
|
|
47
|
+
|
|
48
|
+
**Security checks**:
|
|
49
|
+
- [ ] Authentication/authorization
|
|
50
|
+
- [ ] Input validation
|
|
51
|
+
- [ ] Data encryption
|
|
52
|
+
- [ ] Audit logging
|
|
53
|
+
|
|
54
|
+
## Acceptance Criteria
|
|
55
|
+
|
|
56
|
+
<!-- What must be true for this to be considered complete? -->
|
|
57
|
+
|
|
58
|
+
- [ ] Criterion 1
|
|
59
|
+
- [ ] Criterion 2
|
|
60
|
+
- [ ] Criterion 3
|
|
61
|
+
|
|
62
|
+
## Test Data
|
|
63
|
+
|
|
64
|
+
<!-- What test data is needed? -->
|
|
65
|
+
|
|
66
|
+
## Manual Testing
|
|
67
|
+
|
|
68
|
+
<!-- What requires human verification? -->
|
|
69
|
+
|
|
70
|
+
- [ ] Manual test 1
|
|
71
|
+
- [ ] Manual test 2
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# API Refactor Demo
|
|
2
|
+
|
|
3
|
+
> **Tutorial**: [Refactoring with Specs](https://harnspec.github.io/docs/tutorials/refactoring-specs)
|
|
4
|
+
|
|
5
|
+
## Scenario
|
|
6
|
+
|
|
7
|
+
You're maintaining a Node.js application that started simple but has grown messy. The app integrates with multiple external services (weather API, currency converter, timezone lookup), but all the HTTP logic is tangled together in the main application code.
|
|
8
|
+
|
|
9
|
+
You want to extract a reusable API client module that:
|
|
10
|
+
|
|
11
|
+
- Centralizes HTTP request handling
|
|
12
|
+
- Provides a clean interface for service calls
|
|
13
|
+
- Handles errors consistently
|
|
14
|
+
- Makes the code easier to test and maintain
|
|
15
|
+
|
|
16
|
+
## What's Here
|
|
17
|
+
|
|
18
|
+
A monolithic Node.js app with:
|
|
19
|
+
|
|
20
|
+
- Weather lookup feature (calls external API)
|
|
21
|
+
- Currency conversion (calls external API)
|
|
22
|
+
- Timezone lookup (calls external API)
|
|
23
|
+
- All HTTP logic mixed into business logic (tight coupling)
|
|
24
|
+
- No error handling abstraction
|
|
25
|
+
- Hard to test individual parts
|
|
26
|
+
|
|
27
|
+
**Files:**
|
|
28
|
+
|
|
29
|
+
- `src/app.js` - Main application with all features
|
|
30
|
+
- `src/services/weatherService.js` - Weather API calls (tightly coupled)
|
|
31
|
+
- `src/services/currencyService.js` - Currency API calls (tightly coupled)
|
|
32
|
+
- `src/services/timezoneService.js` - Timezone API calls (tightly coupled)
|
|
33
|
+
|
|
34
|
+
## Getting Started
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install dependencies
|
|
38
|
+
npm install
|
|
39
|
+
|
|
40
|
+
# Run the app
|
|
41
|
+
npm start
|
|
42
|
+
|
|
43
|
+
# Try the features:
|
|
44
|
+
# - Weather: Get weather for a city
|
|
45
|
+
# - Currency: Convert between currencies
|
|
46
|
+
# - Timezone: Look up timezone info
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Your Mission
|
|
50
|
+
|
|
51
|
+
Refactor the HTTP logic into a clean, reusable API client. Follow the tutorial and ask your AI assistant:
|
|
52
|
+
|
|
53
|
+
> "Help me refactor this app using HarnSpec. I want to extract a reusable API client module that centralizes all the HTTP logic."
|
|
54
|
+
|
|
55
|
+
The AI will guide you through:
|
|
56
|
+
|
|
57
|
+
1. Creating a refactoring spec
|
|
58
|
+
2. Designing the API client interface
|
|
59
|
+
3. Extracting the HTTP logic step by step
|
|
60
|
+
4. Updating services to use the new client
|
|
61
|
+
5. Verifying everything still works
|
|
62
|
+
|
|
63
|
+
## Current Problems
|
|
64
|
+
|
|
65
|
+
- **Duplicated code**: Each service reimplements HTTP requests
|
|
66
|
+
- **No error handling**: Errors handled inconsistently
|
|
67
|
+
- **Hard to test**: Can't mock HTTP calls easily
|
|
68
|
+
- **Tight coupling**: Business logic mixed with HTTP details
|
|
69
|
+
- **No retry logic**: Network failures aren't handled
|
|
70
|
+
|
|
71
|
+
These are perfect opportunities to practice refactoring with specs!
|
|
72
|
+
|
|
73
|
+
## Expected Result
|
|
74
|
+
|
|
75
|
+
After refactoring, you should have:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
src/
|
|
79
|
+
app.js (unchanged interface)
|
|
80
|
+
client/
|
|
81
|
+
apiClient.js (new - centralized HTTP logic)
|
|
82
|
+
services/
|
|
83
|
+
weatherService.js (simplified - uses apiClient)
|
|
84
|
+
currencyService.js (simplified - uses apiClient)
|
|
85
|
+
timezoneService.js (simplified - uses apiClient)
|
|
86
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "api-refactor-demo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Monolithic Node.js app for HarnSpec Tutorial 3",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node src/app.js",
|
|
8
|
+
"dev": "node --watch src/app.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["harnspec", "tutorial", "demo"],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"node-fetch": "^3.3.2"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getWeather } from './services/weatherService.js';
|
|
2
|
+
import { convertCurrency } from './services/currencyService.js';
|
|
3
|
+
import { getTimezone } from './services/timezoneService.js';
|
|
4
|
+
|
|
5
|
+
console.log('=== Multi-Service App Demo ===\n');
|
|
6
|
+
|
|
7
|
+
// Demo 1: Weather lookup
|
|
8
|
+
console.log('1. Weather Lookup:');
|
|
9
|
+
try {
|
|
10
|
+
const weather = await getWeather('London');
|
|
11
|
+
console.log(` ${weather.city}: ${weather.temp}°C, ${weather.condition}`);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.log(` Error: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log('');
|
|
17
|
+
|
|
18
|
+
// Demo 2: Currency conversion
|
|
19
|
+
console.log('2. Currency Conversion:');
|
|
20
|
+
try {
|
|
21
|
+
const result = await convertCurrency(100, 'USD', 'EUR');
|
|
22
|
+
console.log(` ${result.amount} ${result.from} = ${result.converted} ${result.to}`);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.log(` Error: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log('');
|
|
28
|
+
|
|
29
|
+
// Demo 3: Timezone lookup
|
|
30
|
+
console.log('3. Timezone Lookup:');
|
|
31
|
+
try {
|
|
32
|
+
const timezone = await getTimezone('America/New_York');
|
|
33
|
+
console.log(` ${timezone.name}: ${timezone.offset} (${timezone.abbreviation})`);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.log(` Error: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log('Notice: All services work, but they all duplicate HTTP logic!');
|
|
40
|
+
console.log('Your task: Extract a reusable API client module.');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert currency
|
|
5
|
+
*
|
|
6
|
+
* PROBLEM: Duplicates HTTP logic from weatherService
|
|
7
|
+
* - Same fetch boilerplate
|
|
8
|
+
* - Same error handling pattern
|
|
9
|
+
* - Same JSON parsing
|
|
10
|
+
* - Should be using a shared HTTP client!
|
|
11
|
+
*/
|
|
12
|
+
export async function convertCurrency(amount, from, to) {
|
|
13
|
+
// Mock API endpoint (replace with real API in production)
|
|
14
|
+
const url = `https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=${amount}`;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(url);
|
|
18
|
+
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`Currency API error: ${response.status}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
|
|
25
|
+
// Business logic: Transform API response to our format
|
|
26
|
+
return {
|
|
27
|
+
amount,
|
|
28
|
+
from,
|
|
29
|
+
to,
|
|
30
|
+
converted: data.result || (amount * 0.85).toFixed(2),
|
|
31
|
+
rate: data.info?.rate || 0.85,
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// For demo, return mock data on error
|
|
35
|
+
return {
|
|
36
|
+
amount,
|
|
37
|
+
from,
|
|
38
|
+
to,
|
|
39
|
+
converted: (amount * 0.85).toFixed(2),
|
|
40
|
+
rate: 0.85,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get timezone information
|
|
5
|
+
*
|
|
6
|
+
* PROBLEM: Yet another copy of the same HTTP logic!
|
|
7
|
+
* - Third time we're writing fetch + error handling + JSON parsing
|
|
8
|
+
* - Violates DRY principle
|
|
9
|
+
* - Makes testing hard (need to mock fetch in 3 places)
|
|
10
|
+
* - Changes to HTTP logic need updates in 3 files
|
|
11
|
+
*/
|
|
12
|
+
export async function getTimezone(zone) {
|
|
13
|
+
// Mock API endpoint (replace with real API in production)
|
|
14
|
+
const url = `http://worldtimeapi.org/api/timezone/${zone}`;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(url);
|
|
18
|
+
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`Timezone API error: ${response.status}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
|
|
25
|
+
// Business logic: Transform API response to our format
|
|
26
|
+
return {
|
|
27
|
+
name: data.timezone || zone,
|
|
28
|
+
offset: data.utc_offset || '-05:00',
|
|
29
|
+
abbreviation: data.abbreviation || 'EST',
|
|
30
|
+
datetime: data.datetime || new Date().toISOString(),
|
|
31
|
+
};
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// For demo, return mock data on error
|
|
34
|
+
return {
|
|
35
|
+
name: zone,
|
|
36
|
+
offset: '-05:00',
|
|
37
|
+
abbreviation: 'EST',
|
|
38
|
+
datetime: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get weather for a city
|
|
5
|
+
*
|
|
6
|
+
* PROBLEM: This function has HTTP logic mixed with business logic
|
|
7
|
+
* - Manual fetch calls
|
|
8
|
+
* - Manual error handling
|
|
9
|
+
* - Manual JSON parsing
|
|
10
|
+
* - No retry logic
|
|
11
|
+
* - Hard to test (can't mock HTTP)
|
|
12
|
+
*/
|
|
13
|
+
export async function getWeather(city) {
|
|
14
|
+
// Mock API endpoint (replace with real API in production)
|
|
15
|
+
const url = `https://api.weatherapi.com/v1/current.json?key=mock&q=${city}`;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url);
|
|
19
|
+
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
throw new Error(`Weather API error: ${response.status}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
|
|
26
|
+
// Business logic: Transform API response to our format
|
|
27
|
+
return {
|
|
28
|
+
city: data.location?.name || city,
|
|
29
|
+
temp: data.current?.temp_c || Math.floor(Math.random() * 30),
|
|
30
|
+
condition: data.current?.condition?.text || 'Sunny',
|
|
31
|
+
humidity: data.current?.humidity || Math.floor(Math.random() * 100),
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// For demo, return mock data on error
|
|
35
|
+
return {
|
|
36
|
+
city,
|
|
37
|
+
temp: Math.floor(Math.random() * 30),
|
|
38
|
+
condition: 'Sunny (mock data)',
|
|
39
|
+
humidity: Math.floor(Math.random() * 100),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|