@skilly-hand/skilly-hand 0.29.1 → 0.29.3
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/CHANGELOG.md +31 -0
- package/catalog/README.md +2 -2
- package/catalog/skills/figma-mcp-0to1/SKILL.md +13 -14
- package/catalog/skills/figma-mcp-0to1/agents/canvas-creation-playbook.md +15 -2
- package/catalog/skills/figma-mcp-0to1/agents/install-auth.md +8 -8
- package/catalog/skills/figma-mcp-0to1/agents/tool-function-catalog.md +11 -5
- package/catalog/skills/figma-mcp-0to1/agents/troubleshooting-ops.md +8 -4
- package/catalog/skills/figma-mcp-0to1/assets/client-config-snippets.md +3 -8
- package/catalog/skills/figma-mcp-0to1/assets/prompt-recipes.md +19 -4
- package/catalog/skills/figma-mcp-0to1/manifest.json +3 -3
- package/catalog/skills/figma-mcp-0to1/references/official-tools-matrix.md +27 -28
- package/catalog/skills/spec-driven-development/SKILL.md +95 -144
- package/catalog/skills/spec-driven-development/agents/apply.md +30 -15
- package/catalog/skills/spec-driven-development/agents/orchestrate.md +23 -14
- package/catalog/skills/spec-driven-development/agents/plan.md +19 -17
- package/catalog/skills/spec-driven-development/agents/verify.md +40 -19
- package/catalog/skills/spec-driven-development/assets/delta-spec-template.md +50 -15
- package/catalog/skills/spec-driven-development/assets/design-template.md +20 -14
- package/catalog/skills/spec-driven-development/assets/spec-template.md +41 -20
- package/catalog/skills/spec-driven-development/assets/validation-checklist.md +28 -21
- package/catalog/skills/spec-driven-development/manifest.json +4 -4
- package/catalog/skills/test-driven-development/SKILL.md +92 -117
- package/catalog/skills/test-driven-development/assets/tdd-cycle.md +63 -447
- package/catalog/skills/test-driven-development/manifest.json +5 -5
- package/package.json +1 -1
- package/packages/catalog/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/core/package.json +1 -1
- package/packages/detectors/package.json +1 -1
|
@@ -1,487 +1,103 @@
|
|
|
1
|
-
# TDD
|
|
1
|
+
# Portable TDD Cycle Examples
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
These examples use pseudocode so the cycle can be translated to the project's language, test runner, and conventions.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Example 1: New Behavior
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Behavior: normalize surrounding whitespace from a user-provided label.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### RED
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// async-cache.pipe.test.ts
|
|
18
|
-
import { TestBed } from '@angular/core/testing';
|
|
19
|
-
import { Subject, take } from 'rxjs';
|
|
20
|
-
import { AsyncCachePipe } from './async-cache.pipe';
|
|
21
|
-
|
|
22
|
-
describe('AsyncCachePipe', () => {
|
|
23
|
-
let pipe: AsyncCachePipe;
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
TestBed.configureTestingModule({});
|
|
27
|
-
pipe = TestBed.runInInjectionContext(() => new AsyncCachePipe());
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should cache async values and replay on late subscriptions', (done) => {
|
|
31
|
-
// GIVEN: An observable that emits after 100ms
|
|
32
|
-
let emitCount = 0;
|
|
33
|
-
const source$ = new Subject<number>();
|
|
34
|
-
|
|
35
|
-
// WHEN: First subscription
|
|
36
|
-
const result$ = pipe.transform(source$);
|
|
37
|
-
const values: number[] = [];
|
|
38
|
-
|
|
39
|
-
const sub1 = result$.subscribe((val) => {
|
|
40
|
-
emitCount++;
|
|
41
|
-
values.push(val);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
setTimeout(() => {
|
|
45
|
-
source$.next(42);
|
|
46
|
-
}, 100);
|
|
47
|
-
|
|
48
|
-
// WHEN: Second subscription arrives after value is cached
|
|
49
|
-
setTimeout(() => {
|
|
50
|
-
const sub2 = result$.pipe(take(1)).subscribe(() => {
|
|
51
|
-
// THEN: Second subscriber gets cached value immediately
|
|
52
|
-
expect(emitCount).toBe(1);
|
|
53
|
-
expect(values[0]).toBe(42);
|
|
54
|
-
|
|
55
|
-
sub1.unsubscribe();
|
|
56
|
-
sub2.unsubscribe();
|
|
57
|
-
source$.complete();
|
|
58
|
-
done();
|
|
59
|
-
});
|
|
60
|
-
}, 200);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// ❌ TEST FAILS HERE - transform() not implemented yet
|
|
64
|
-
});
|
|
11
|
+
```text
|
|
12
|
+
test "normalizes surrounding whitespace":
|
|
13
|
+
result = normalize_label(" Ready ")
|
|
14
|
+
expect result equals "Ready"
|
|
65
15
|
```
|
|
66
16
|
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
FAIL async-cache.pipe.test.ts
|
|
70
|
-
should cache async values and replay on late subscriptions
|
|
71
|
-
TypeError: pipe.transform is not a function
|
|
72
|
-
```
|
|
17
|
+
Run the project's focused test command. Confirm it fails because normalization is missing, not because the test cannot load or compile.
|
|
73
18
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
#### Step 2: GREEN — Write Minimum Code
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
// async-cache.pipe.ts
|
|
82
|
-
import { Pipe, PipeTransform } from '@angular/core';
|
|
83
|
-
import { Observable, ReplaySubject } from 'rxjs';
|
|
84
|
-
import { tap } from 'rxjs/operators';
|
|
85
|
-
|
|
86
|
-
@Pipe({
|
|
87
|
-
name: 'asyncCache',
|
|
88
|
-
standalone: true,
|
|
89
|
-
})
|
|
90
|
-
export class AsyncCachePipe implements PipeTransform {
|
|
91
|
-
// ✅ MINIMUM: Just make the test pass
|
|
92
|
-
transform<T>(source: Observable<T>): Observable<T> {
|
|
93
|
-
const cached$ = new ReplaySubject<T>(1);
|
|
94
|
-
source.pipe(tap((val) => cached$.next(val))).subscribe();
|
|
95
|
-
return cached$.asObservable();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
19
|
+
### GREEN
|
|
99
20
|
|
|
100
|
-
|
|
21
|
+
```text
|
|
22
|
+
function normalize_label(value):
|
|
23
|
+
return trim(value)
|
|
101
24
|
```
|
|
102
|
-
PASS async-cache.pipe.test.ts
|
|
103
|
-
should cache async values and replay on late subscriptions ✓
|
|
104
25
|
|
|
105
|
-
|
|
106
|
-
```
|
|
26
|
+
Run the focused test and relevant nearby tests. They pass.
|
|
107
27
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
#### Step 3: REFACTOR — Improve Without Changing Behavior
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// async-cache.pipe.ts (refactored)
|
|
116
|
-
import { Pipe, PipeTransform } from '@angular/core';
|
|
117
|
-
import { Observable, ReplaySubject, Subject, takeUntil } from 'rxjs';
|
|
118
|
-
import { tap } from 'rxjs/operators';
|
|
119
|
-
|
|
120
|
-
@Pipe({
|
|
121
|
-
name: 'asyncCache',
|
|
122
|
-
standalone: true,
|
|
123
|
-
})
|
|
124
|
-
export class AsyncCachePipe implements PipeTransform {
|
|
125
|
-
private destroy$ = new Subject<void>();
|
|
126
|
-
|
|
127
|
-
// ✅ REFACTORED: Cleaner, adds cleanup
|
|
128
|
-
transform<T>(source: Observable<T>): Observable<T> {
|
|
129
|
-
const cacheSize = 1;
|
|
130
|
-
const cached$ = new ReplaySubject<T>(cacheSize);
|
|
131
|
-
|
|
132
|
-
source
|
|
133
|
-
.pipe(
|
|
134
|
-
tap((val) => cached$.next(val)),
|
|
135
|
-
takeUntil(this.destroy$)
|
|
136
|
-
)
|
|
137
|
-
.subscribe();
|
|
138
|
-
|
|
139
|
-
return cached$.asObservable();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
ngOnDestroy(): void {
|
|
143
|
-
this.destroy$.next();
|
|
144
|
-
this.destroy$.complete();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
```
|
|
28
|
+
### REFACTOR
|
|
148
29
|
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
PASS async-cache.pipe.test.ts
|
|
152
|
-
should cache async values and replay on late subscriptions ✓
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
✅ Test still passes
|
|
156
|
-
|
|
157
|
-
**What improved**:
|
|
158
|
-
- Constants extracted (`cacheSize`)
|
|
159
|
-
- Cleanup logic added (`takeUntil`, `ngOnDestroy`)
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Example 2: Component with Input/Output
|
|
164
|
-
|
|
165
|
-
### Scenario: AlertComponent
|
|
166
|
-
Display a dismissible alert with a message.
|
|
167
|
-
|
|
168
|
-
#### Step 1: RED — Write Failing Test
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
// alert.component.test.ts
|
|
172
|
-
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
173
|
-
import { AlertComponent } from './alert.component';
|
|
30
|
+
No structural improvement is needed. Record `NOT_NEEDED`; REFACTOR is an opportunity, not a quota.
|
|
174
31
|
|
|
175
|
-
|
|
176
|
-
let component: AlertComponent;
|
|
177
|
-
let fixture: ComponentFixture<AlertComponent>;
|
|
32
|
+
### Next Behavior Is a New Cycle
|
|
178
33
|
|
|
179
|
-
|
|
180
|
-
await TestBed.configureTestingModule({
|
|
181
|
-
imports: [AlertComponent],
|
|
182
|
-
}).compileComponents();
|
|
34
|
+
Rejecting an empty normalized label would be new observable behavior. Do not add it during REFACTOR. Write a failing empty-label test first.
|
|
183
35
|
|
|
184
|
-
|
|
185
|
-
component = fixture.componentInstance;
|
|
186
|
-
});
|
|
36
|
+
## Example 2: Regression
|
|
187
37
|
|
|
188
|
-
|
|
189
|
-
// GIVEN: Component with message input
|
|
190
|
-
component.message = 'Error: Invalid input';
|
|
191
|
-
fixture.detectChanges();
|
|
38
|
+
Defect: a retry policy performs one extra attempt.
|
|
192
39
|
|
|
193
|
-
|
|
194
|
-
const messageEl = fixture.debugElement.nativeElement.querySelector('[data-testid="alert-message"]');
|
|
40
|
+
### RED
|
|
195
41
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
42
|
+
```text
|
|
43
|
+
test "stops after configured attempts":
|
|
44
|
+
dependency = failing_fake()
|
|
45
|
+
retry(dependency, attempts = 3)
|
|
46
|
+
expect dependency call_count equals 3
|
|
202
47
|
```
|
|
203
48
|
|
|
204
|
-
|
|
205
|
-
```
|
|
206
|
-
FAIL alert.component.test.ts
|
|
207
|
-
Cannot find component 'AlertComponent'
|
|
208
|
-
```
|
|
49
|
+
Confirm the faulty baseline reports four calls. A load error or unrelated exception is not valid RED evidence.
|
|
209
50
|
|
|
210
|
-
|
|
51
|
+
### GREEN
|
|
211
52
|
|
|
212
|
-
|
|
53
|
+
Change only the attempt boundary. Confirm the regression test now reports three calls and nearby retry tests still pass.
|
|
213
54
|
|
|
214
|
-
|
|
215
|
-
// alert.component.ts
|
|
216
|
-
import { Component, input } from '@angular/core';
|
|
55
|
+
### REFACTOR
|
|
217
56
|
|
|
218
|
-
|
|
219
|
-
selector: 'app-alert',
|
|
220
|
-
standalone: true,
|
|
221
|
-
template: `<div data-testid="alert-message">{{ message() }}</div>`,
|
|
222
|
-
})
|
|
223
|
-
export class AlertComponent {
|
|
224
|
-
// ✅ MINIMUM: Just the message input
|
|
225
|
-
message = input<string>('');
|
|
226
|
-
}
|
|
227
|
-
```
|
|
57
|
+
Rename the internal loop counter if that improves clarity. Do not add backoff, logging, or a new error type without separate RED tests.
|
|
228
58
|
|
|
229
|
-
|
|
230
|
-
```
|
|
231
|
-
PASS alert.component.test.ts
|
|
232
|
-
should display the alert message ✓
|
|
233
|
-
```
|
|
59
|
+
## Example 3: Test Already Passes
|
|
234
60
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
#### Step 3: REFACTOR — Add Accessibility and Dismiss
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
// alert.component.ts (refactored)
|
|
241
|
-
import { Component, input, output, ChangeDetectionStrategy } from '@angular/core';
|
|
242
|
-
|
|
243
|
-
@Component({
|
|
244
|
-
selector: 'app-alert',
|
|
245
|
-
standalone: true,
|
|
246
|
-
template: `
|
|
247
|
-
<div
|
|
248
|
-
class="alert"
|
|
249
|
-
role="alert"
|
|
250
|
-
aria-live="polite"
|
|
251
|
-
[attr.data-testid]="'alert-message'"
|
|
252
|
-
[class]="'alert--' + severity()">
|
|
253
|
-
<p class="alert__message">{{ message() }}</p>
|
|
254
|
-
<button class="alert__close" aria-label="Dismiss alert" (click)="onDismiss()">✕</button>
|
|
255
|
-
</div>
|
|
256
|
-
`,
|
|
257
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
258
|
-
})
|
|
259
|
-
export class AlertComponent {
|
|
260
|
-
message = input<string>('');
|
|
261
|
-
severity = input<'success' | 'error' | 'warning' | 'info'>('info');
|
|
262
|
-
dismissed = output<void>();
|
|
263
|
-
|
|
264
|
-
onDismiss(): void {
|
|
265
|
-
this.dismissed.emit();
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
```
|
|
61
|
+
When a proposed RED test passes:
|
|
269
62
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
✅ Test still passes
|
|
277
|
-
|
|
278
|
-
**What improved**:
|
|
279
|
-
- Accessibility attributes (`role="alert"`, `aria-live`, `aria-label`)
|
|
280
|
-
- Dismiss output and handler
|
|
281
|
-
- Severity levels
|
|
282
|
-
- `ChangeDetectionStrategy.OnPush`
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## Example 3: Service with State Management
|
|
287
|
-
|
|
288
|
-
### Scenario: TodoService
|
|
289
|
-
Manage a todo list with add and complete operations.
|
|
290
|
-
|
|
291
|
-
#### Step 1: RED — Multiple Scenarios
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
// todo.service.test.ts
|
|
295
|
-
import { TestBed } from '@angular/core/testing';
|
|
296
|
-
import { TodoService } from './todo.service';
|
|
297
|
-
|
|
298
|
-
describe('TodoService', () => {
|
|
299
|
-
let service: TodoService;
|
|
300
|
-
|
|
301
|
-
beforeEach(() => {
|
|
302
|
-
TestBed.configureTestingModule({ providers: [TodoService] });
|
|
303
|
-
service = TestBed.inject(TodoService);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('should add a todo and update the list', () => {
|
|
307
|
-
// GIVEN: Empty list
|
|
308
|
-
// WHEN: Add todo
|
|
309
|
-
service.addTodo('Buy milk');
|
|
310
|
-
|
|
311
|
-
// THEN: Todo appears
|
|
312
|
-
expect(service.todos()).toContainEqual(
|
|
313
|
-
jasmine.objectContaining({ text: 'Buy milk', done: false })
|
|
314
|
-
);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('should mark a todo as complete', () => {
|
|
318
|
-
// GIVEN: A todo in the list
|
|
319
|
-
service.addTodo('Buy milk');
|
|
320
|
-
const firstTodo = service.todos()[0];
|
|
321
|
-
|
|
322
|
-
// WHEN: Mark as complete
|
|
323
|
-
service.completeTodo(firstTodo.id);
|
|
324
|
-
|
|
325
|
-
// THEN: Todo is done
|
|
326
|
-
expect(service.todos()[0].done).toBe(true);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// ❌ FAILS - Service methods don't exist
|
|
330
|
-
});
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
**Run**: `npm test -- todo.service.test.ts`
|
|
334
|
-
```
|
|
335
|
-
FAIL todo.service.test.ts
|
|
336
|
-
service.addTodo is not a function
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
---
|
|
340
|
-
|
|
341
|
-
#### Step 2: GREEN — Write Minimum Implementation
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
// todo.service.ts
|
|
345
|
-
import { Injectable, signal } from '@angular/core';
|
|
346
|
-
|
|
347
|
-
interface Todo { id: number; text: string; done: boolean; }
|
|
63
|
+
1. Run it against the unchanged baseline again.
|
|
64
|
+
2. Check whether the behavior already exists.
|
|
65
|
+
3. Check whether the assertion observes the intended public outcome.
|
|
66
|
+
4. Check whether setup bypasses the relevant code path.
|
|
67
|
+
5. Revise the scenario only when the requirement was misunderstood.
|
|
348
68
|
|
|
349
|
-
|
|
350
|
-
export class TodoService {
|
|
351
|
-
todos = signal<Todo[]>([]);
|
|
352
|
-
private nextId = 1;
|
|
69
|
+
Do not intentionally break production code merely to manufacture a RED result.
|
|
353
70
|
|
|
354
|
-
|
|
355
|
-
this.todos.update((current) => [...current, { id: this.nextId++, text, done: false }]);
|
|
356
|
-
}
|
|
71
|
+
## Evidence Template
|
|
357
72
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
current.map((todo) => todo.id === id ? { ...todo, done: true } : todo)
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
```
|
|
73
|
+
```markdown
|
|
74
|
+
### TDD Evidence: [Behavior]
|
|
365
75
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
76
|
+
- Test level: [unit | integration | contract | end-to-end | characterization]
|
|
77
|
+
- Focused check: [project-discovered command or procedure]
|
|
78
|
+
- RED: `FAIL` - [expected failure reason]
|
|
79
|
+
- GREEN: `PASS` - [minimum behavior implemented]
|
|
80
|
+
- REFACTOR: `PASS` or `NOT_NEEDED` - [structural change only]
|
|
81
|
+
- Regression: `PASS`, `FAIL`, or `NOT_RUN` - [scope and reason]
|
|
82
|
+
- Notes: [test doubles, manual checks, or limitations]
|
|
371
83
|
```
|
|
372
84
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
#### Step 3: REFACTOR — Add Validation and Persistence
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
// todo.service.ts (refactored)
|
|
379
|
-
import { Injectable, signal } from '@angular/core';
|
|
380
|
-
|
|
381
|
-
interface Todo {
|
|
382
|
-
id: number;
|
|
383
|
-
text: string;
|
|
384
|
-
done: boolean;
|
|
385
|
-
createdAt: Date;
|
|
386
|
-
completedAt?: Date;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
@Injectable({ providedIn: 'root' })
|
|
390
|
-
export class TodoService {
|
|
391
|
-
todos = signal<Todo[]>([]);
|
|
392
|
-
private nextId = 1;
|
|
393
|
-
private readonly MAX_TODOS = 100;
|
|
394
|
-
|
|
395
|
-
constructor() {
|
|
396
|
-
this.loadFromStorage();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
addTodo(text: string): void {
|
|
400
|
-
if (!text?.trim()) throw new Error('Todo text cannot be empty');
|
|
401
|
-
if (this.todos().length >= this.MAX_TODOS) throw new Error(`Cannot exceed ${this.MAX_TODOS} todos`);
|
|
402
|
-
|
|
403
|
-
this.todos.update((current) => [
|
|
404
|
-
...current,
|
|
405
|
-
{ id: this.nextId++, text: text.trim(), done: false, createdAt: new Date() },
|
|
406
|
-
]);
|
|
407
|
-
this.saveToStorage();
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
completeTodo(id: number): void {
|
|
411
|
-
if (!this.todos().find((t) => t.id === id)) throw new Error(`Todo with id ${id} not found`);
|
|
412
|
-
|
|
413
|
-
this.todos.update((current) =>
|
|
414
|
-
current.map((t) => t.id === id ? { ...t, done: true, completedAt: new Date() } : t)
|
|
415
|
-
);
|
|
416
|
-
this.saveToStorage();
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
deleteTodo(id: number): void {
|
|
420
|
-
this.todos.update((current) => current.filter((t) => t.id !== id));
|
|
421
|
-
this.saveToStorage();
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
private loadFromStorage(): void {
|
|
425
|
-
try {
|
|
426
|
-
const stored = localStorage.getItem('todos');
|
|
427
|
-
if (stored) this.todos.set(JSON.parse(stored));
|
|
428
|
-
} catch (e) {
|
|
429
|
-
console.error('Failed to load todos from storage', e);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
private saveToStorage(): void {
|
|
434
|
-
try {
|
|
435
|
-
localStorage.setItem('todos', JSON.stringify(this.todos()));
|
|
436
|
-
} catch (e) {
|
|
437
|
-
console.error('Failed to save todos to storage', e);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
```
|
|
85
|
+
## Cycle Checklist
|
|
442
86
|
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
PASS todo.service.test.ts
|
|
446
|
-
should add a todo and update the list ✓
|
|
447
|
-
should mark a todo as complete ✓
|
|
448
|
-
```
|
|
87
|
+
### RED
|
|
449
88
|
|
|
450
|
-
|
|
89
|
+
- [ ] One observable behavior is defined.
|
|
90
|
+
- [ ] The test was added before production behavior changed.
|
|
91
|
+
- [ ] The test fails for the expected reason.
|
|
451
92
|
|
|
452
|
-
|
|
453
|
-
- Input validation (empty check, max limit)
|
|
454
|
-
- Timestamps (`createdAt`, `completedAt`)
|
|
455
|
-
- Persistence via `localStorage`
|
|
456
|
-
- `deleteTodo` added for completeness
|
|
93
|
+
### GREEN
|
|
457
94
|
|
|
458
|
-
|
|
95
|
+
- [ ] The smallest required behavior was implemented.
|
|
96
|
+
- [ ] The focused test passes.
|
|
97
|
+
- [ ] Relevant regression checks pass or are honestly marked `NOT_RUN`.
|
|
459
98
|
|
|
460
|
-
|
|
99
|
+
### REFACTOR
|
|
461
100
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
#### RED Phase
|
|
466
|
-
- [ ] Write test that describes the feature
|
|
467
|
-
- [ ] Test has explicit GIVEN / WHEN / THEN
|
|
468
|
-
- [ ] Run tests — FAILS as expected
|
|
469
|
-
- [ ] Failure proves test is meaningful
|
|
470
|
-
|
|
471
|
-
#### GREEN Phase
|
|
472
|
-
- [ ] Write minimum code to pass
|
|
473
|
-
- [ ] No extra features beyond test requirement
|
|
474
|
-
- [ ] Run tests — PASSES
|
|
475
|
-
|
|
476
|
-
#### REFACTOR Phase
|
|
477
|
-
- [ ] Improve code structure / naming
|
|
478
|
-
- [ ] Extract constants, simplify logic
|
|
479
|
-
- [ ] Run tests — STILL PASSES
|
|
480
|
-
- [ ] No behavior changes, only improvements
|
|
481
|
-
|
|
482
|
-
#### Verify
|
|
483
|
-
- [ ] All tests pass: `npm test`
|
|
484
|
-
- [ ] Lint passes: `npm run lint`
|
|
485
|
-
- [ ] Type check: `npx tsc --noEmit`
|
|
486
|
-
- [ ] Build succeeds: `npm run build`
|
|
487
|
-
```
|
|
101
|
+
- [ ] Structural changes preserve observable behavior.
|
|
102
|
+
- [ ] No untested feature or edge case was added.
|
|
103
|
+
- [ ] Tests remain green, or refactoring is recorded as `NOT_NEEDED`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "test-driven-development",
|
|
3
3
|
"title": "Test-Driven Development",
|
|
4
|
-
"description": "Guide implementation
|
|
4
|
+
"description": "Guide implementation through evidence-based RED, GREEN, and REFACTOR cycles without assuming a language, framework, or test runner. Trigger: implementing testable behavior or reproducing a regression with tests first.",
|
|
5
5
|
"portable": true,
|
|
6
6
|
"tags": ["testing", "workflow", "quality", "core"],
|
|
7
7
|
"detectors": ["always"],
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
"agentSupport": ["codex", "claude", "cursor", "gemini", "copilot", "antigravity", "windsurf", "trae"],
|
|
11
11
|
"skillMetadata": {
|
|
12
12
|
"author": "skilly-hand",
|
|
13
|
-
"last-edit": "2026-
|
|
13
|
+
"last-edit": "2026-06-20",
|
|
14
14
|
"license": "Apache-2.0",
|
|
15
|
-
"version": "1.
|
|
16
|
-
"changelog": "
|
|
17
|
-
"auto-invoke": "Implementing
|
|
15
|
+
"version": "1.1.0",
|
|
16
|
+
"changelog": "Rebuilt TDD guidance around portable cycle evidence, expected RED failures, behavior-preserving refactors, and project-discovered test conventions; prevents framework assumptions and untested behavior during refactor; affects core workflow, examples, and verification guidance",
|
|
17
|
+
"auto-invoke": "Implementing testable behavior or reproducing a regression with tests first",
|
|
18
18
|
"allowed-tools": ["Read", "Edit", "Write", "Glob", "Grep", "Bash"]
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
package/package.json
CHANGED