tdd-enforcer 0.2.6 → 0.2.7
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/.github/workflows/ci.yml +28 -0
- package/adapters/pi/helpers.test.ts +246 -244
- package/adapters/pi/helpers.ts +117 -91
- package/adapters/pi/hooks.test.ts +528 -425
- package/adapters/pi/hooks.ts +297 -230
- package/adapters/pi/index.test.ts +282 -272
- package/adapters/pi/index.ts +222 -200
- package/adapters/pi/log.test.ts +91 -91
- package/adapters/pi/log.ts +39 -27
- package/adapters/pi/prompts.test.ts +25 -25
- package/adapters/pi/prompts.ts +28 -30
- package/adapters/pi/tools.test.ts +391 -331
- package/adapters/pi/tools.ts +368 -325
- package/behaviour.md +15 -1
- package/biome.json +37 -0
- package/engine/config.test.ts +157 -157
- package/engine/config.ts +22 -19
- package/engine/enforce.test.ts +155 -145
- package/engine/enforce.ts +31 -23
- package/engine/git.test.ts +529 -507
- package/engine/git.ts +240 -114
- package/engine/index.ts +27 -4
- package/engine/state.test.ts +69 -69
- package/engine/state.ts +22 -20
- package/engine/transition.test.ts +229 -177
- package/engine/transition.ts +55 -52
- package/engine/types.ts +9 -9
- package/package.json +29 -22
- package/tsconfig.json +10 -10
|
@@ -1,302 +1,312 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
handleTddOff,
|
|
4
|
+
handleTddOn,
|
|
5
|
+
handleTddReset,
|
|
6
|
+
handleTddStatus,
|
|
7
|
+
} from "./index.js";
|
|
3
8
|
|
|
4
9
|
const config = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
blockedInRed: ["tests/**/*.test.ts"],
|
|
11
|
+
blockedInGreen: ["src/**/*.ts"],
|
|
12
|
+
testCommands: ["npm test"],
|
|
13
|
+
timeoutSeconds: 30,
|
|
9
14
|
};
|
|
10
15
|
|
|
11
16
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
12
17
|
|
|
13
18
|
function makeCtx(dir = "/test") {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const notifications: Array<{ message: string; type: string }> = [];
|
|
20
|
+
return {
|
|
21
|
+
cwd: dir,
|
|
22
|
+
ui: {
|
|
23
|
+
notify: (message: string, type: string) =>
|
|
24
|
+
notifications.push({ message, type }),
|
|
25
|
+
},
|
|
26
|
+
notifications, // stored alongside for easy assertion
|
|
27
|
+
} as any;
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
function
|
|
23
|
-
|
|
30
|
+
function _captureNotifications(
|
|
31
|
+
ctx: any,
|
|
32
|
+
): Array<{ message: string; type: string }> {
|
|
33
|
+
return ctx.notifications;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
// ── handleTddOn ─────────────────────────────────────────────────────────────
|
|
27
37
|
|
|
28
38
|
describe("handleTddOn", () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
39
|
+
let mockLoadTddState: ReturnType<typeof vi.fn>;
|
|
40
|
+
let mockSnapshot: ReturnType<typeof vi.fn>;
|
|
41
|
+
let mockSavePhaseState: ReturnType<typeof vi.fn>;
|
|
42
|
+
let mockTddLog: ReturnType<typeof vi.fn>;
|
|
43
|
+
|
|
44
|
+
function makeDeps(overrides = {}) {
|
|
45
|
+
return {
|
|
46
|
+
loadTddState: mockLoadTddState,
|
|
47
|
+
snapshot: mockSnapshot,
|
|
48
|
+
savePhaseState: mockSavePhaseState,
|
|
49
|
+
tddLog: mockTddLog,
|
|
50
|
+
...overrides,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
mockLoadTddState = vi.fn();
|
|
56
|
+
mockSnapshot = vi.fn().mockReturnValue("hash123");
|
|
57
|
+
mockSavePhaseState = vi.fn();
|
|
58
|
+
mockTddLog = vi.fn();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("enables TDD, takes snapshot, notifies user", async () => {
|
|
62
|
+
mockLoadTddState.mockReturnValue({
|
|
63
|
+
ok: true,
|
|
64
|
+
state: { enabled: false, current: "red" },
|
|
65
|
+
config,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const ctx = makeCtx();
|
|
69
|
+
await handleTddOn(ctx, makeDeps());
|
|
70
|
+
|
|
71
|
+
expect(mockSnapshot).toHaveBeenCalledWith("/test", "red");
|
|
72
|
+
expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
|
|
73
|
+
enabled: true,
|
|
74
|
+
current: "red",
|
|
75
|
+
});
|
|
76
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
77
|
+
expect(ctx.notifications[0].message).toContain("TDD enabled");
|
|
78
|
+
expect(ctx.notifications[0].type).toBe("info");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("shows already enabled when TDD already on", async () => {
|
|
82
|
+
mockLoadTddState.mockReturnValue({
|
|
83
|
+
ok: true,
|
|
84
|
+
state: { enabled: true, current: "red" },
|
|
85
|
+
config,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const ctx = makeCtx();
|
|
89
|
+
await handleTddOn(ctx, makeDeps());
|
|
90
|
+
|
|
91
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
92
|
+
expect(ctx.notifications[0].message).toContain("already enabled");
|
|
93
|
+
expect(ctx.notifications[0].type).toBe("info");
|
|
94
|
+
expect(mockSnapshot).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("shows error when setup invalid", async () => {
|
|
98
|
+
mockLoadTddState.mockReturnValue({
|
|
99
|
+
ok: false,
|
|
100
|
+
reason: "Missing .pi/tdd/",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const ctx = makeCtx();
|
|
104
|
+
await handleTddOn(ctx, makeDeps());
|
|
105
|
+
|
|
106
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
107
|
+
expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
|
|
108
|
+
expect(ctx.notifications[0].type).toBe("error");
|
|
109
|
+
});
|
|
100
110
|
});
|
|
101
111
|
|
|
102
112
|
// ── handleTddOff ────────────────────────────────────────────────────────────
|
|
103
113
|
|
|
104
114
|
describe("handleTddOff", () => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
115
|
+
let mockLoadTddState: ReturnType<typeof vi.fn>;
|
|
116
|
+
let mockSavePhaseState: ReturnType<typeof vi.fn>;
|
|
117
|
+
let mockTddLog: ReturnType<typeof vi.fn>;
|
|
118
|
+
|
|
119
|
+
function makeDeps(overrides = {}) {
|
|
120
|
+
return {
|
|
121
|
+
loadTddState: mockLoadTddState,
|
|
122
|
+
savePhaseState: mockSavePhaseState,
|
|
123
|
+
tddLog: mockTddLog,
|
|
124
|
+
...overrides,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
mockLoadTddState = vi.fn();
|
|
130
|
+
mockSavePhaseState = vi.fn();
|
|
131
|
+
mockTddLog = vi.fn();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("disables TDD, notifies user", async () => {
|
|
135
|
+
mockLoadTddState.mockReturnValue({
|
|
136
|
+
ok: true,
|
|
137
|
+
state: { enabled: true, current: "red" },
|
|
138
|
+
config,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const ctx = makeCtx();
|
|
142
|
+
await handleTddOff(ctx, makeDeps());
|
|
143
|
+
|
|
144
|
+
expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
|
|
145
|
+
enabled: false,
|
|
146
|
+
current: "red",
|
|
147
|
+
});
|
|
148
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
149
|
+
expect(ctx.notifications[0].message).toContain("disabled");
|
|
150
|
+
expect(ctx.notifications[0].type).toBe("info");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("shows already disabled when TDD already off", async () => {
|
|
154
|
+
mockLoadTddState.mockReturnValue({
|
|
155
|
+
ok: true,
|
|
156
|
+
state: { enabled: false, current: "red" },
|
|
157
|
+
config,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const ctx = makeCtx();
|
|
161
|
+
await handleTddOff(ctx, makeDeps());
|
|
162
|
+
|
|
163
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
164
|
+
expect(ctx.notifications[0].message).toContain("already disabled");
|
|
165
|
+
expect(ctx.notifications[0].type).toBe("info");
|
|
166
|
+
expect(mockSavePhaseState).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("shows error when setup invalid", async () => {
|
|
170
|
+
mockLoadTddState.mockReturnValue({
|
|
171
|
+
ok: false,
|
|
172
|
+
reason: "Missing .pi/tdd/",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const ctx = makeCtx();
|
|
176
|
+
await handleTddOff(ctx, makeDeps());
|
|
177
|
+
|
|
178
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
179
|
+
expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
|
|
180
|
+
expect(ctx.notifications[0].type).toBe("error");
|
|
181
|
+
});
|
|
172
182
|
});
|
|
173
183
|
|
|
174
184
|
// ── handleTddStatus ─────────────────────────────────────────────────────────
|
|
175
185
|
|
|
176
186
|
describe("handleTddStatus", () => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
187
|
+
let mockLoadTddState: ReturnType<typeof vi.fn>;
|
|
188
|
+
let mockTddLog: ReturnType<typeof vi.fn>;
|
|
189
|
+
|
|
190
|
+
function makeDeps(overrides = {}) {
|
|
191
|
+
return {
|
|
192
|
+
loadTddState: mockLoadTddState,
|
|
193
|
+
tddLog: mockTddLog,
|
|
194
|
+
...overrides,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
mockLoadTddState = vi.fn();
|
|
200
|
+
mockTddLog = vi.fn();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("shows status when TDD enabled", async () => {
|
|
204
|
+
mockLoadTddState.mockReturnValue({
|
|
205
|
+
ok: true,
|
|
206
|
+
state: { enabled: true, current: "green" },
|
|
207
|
+
config,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const ctx = makeCtx();
|
|
211
|
+
await handleTddStatus(ctx, makeDeps());
|
|
212
|
+
|
|
213
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
214
|
+
expect(ctx.notifications[0].message).toContain("enabled");
|
|
215
|
+
expect(ctx.notifications[0].message).toContain("GREEN");
|
|
216
|
+
expect(ctx.notifications[0].type).toBe("info");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("shows status when TDD disabled", async () => {
|
|
220
|
+
mockLoadTddState.mockReturnValue({
|
|
221
|
+
ok: true,
|
|
222
|
+
state: { enabled: false, current: "red" },
|
|
223
|
+
config,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const ctx = makeCtx();
|
|
227
|
+
await handleTddStatus(ctx, makeDeps());
|
|
228
|
+
|
|
229
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
230
|
+
expect(ctx.notifications[0].message).toContain("disabled");
|
|
231
|
+
expect(ctx.notifications[0].message).toContain("RED");
|
|
232
|
+
expect(ctx.notifications[0].type).toBe("info");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("shows error when setup invalid", async () => {
|
|
236
|
+
mockLoadTddState.mockReturnValue({
|
|
237
|
+
ok: false,
|
|
238
|
+
reason: "Missing .pi/tdd/",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const ctx = makeCtx();
|
|
242
|
+
await handleTddStatus(ctx, makeDeps());
|
|
243
|
+
|
|
244
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
245
|
+
expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
|
|
246
|
+
expect(ctx.notifications[0].type).toBe("error");
|
|
247
|
+
});
|
|
238
248
|
});
|
|
239
249
|
|
|
240
250
|
// ── handleTddReset ──────────────────────────────────────────────────────────
|
|
241
251
|
|
|
242
252
|
describe("handleTddReset", () => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
253
|
+
let mockLoadTddState: ReturnType<typeof vi.fn>;
|
|
254
|
+
let mockResetGit: ReturnType<typeof vi.fn>;
|
|
255
|
+
let mockSnapshot: ReturnType<typeof vi.fn>;
|
|
256
|
+
let mockSavePhaseState: ReturnType<typeof vi.fn>;
|
|
257
|
+
let mockTddLog: ReturnType<typeof vi.fn>;
|
|
258
|
+
|
|
259
|
+
function makeDeps(overrides = {}) {
|
|
260
|
+
return {
|
|
261
|
+
loadTddState: mockLoadTddState,
|
|
262
|
+
resetGit: mockResetGit,
|
|
263
|
+
snapshot: mockSnapshot,
|
|
264
|
+
savePhaseState: mockSavePhaseState,
|
|
265
|
+
tddLog: mockTddLog,
|
|
266
|
+
...overrides,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
mockLoadTddState = vi.fn();
|
|
272
|
+
mockResetGit = vi.fn();
|
|
273
|
+
mockSnapshot = vi.fn().mockReturnValue("hash123");
|
|
274
|
+
mockSavePhaseState = vi.fn();
|
|
275
|
+
mockTddLog = vi.fn();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("nukes git, re-inits, snapshots, resets state to RED disabled", async () => {
|
|
279
|
+
mockLoadTddState.mockReturnValue({
|
|
280
|
+
ok: true,
|
|
281
|
+
state: { enabled: true, current: "green" },
|
|
282
|
+
config,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const ctx = makeCtx();
|
|
286
|
+
await handleTddReset(ctx, makeDeps());
|
|
287
|
+
|
|
288
|
+
expect(mockResetGit).toHaveBeenCalledWith("/test");
|
|
289
|
+
expect(mockSnapshot).toHaveBeenCalledWith("/test", "red");
|
|
290
|
+
expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
|
|
291
|
+
enabled: false,
|
|
292
|
+
current: "red",
|
|
293
|
+
});
|
|
294
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
295
|
+
expect(ctx.notifications[0].message).toContain("reset");
|
|
296
|
+
expect(ctx.notifications[0].type).toBe("warning");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("shows error when setup invalid", async () => {
|
|
300
|
+
mockLoadTddState.mockReturnValue({
|
|
301
|
+
ok: false,
|
|
302
|
+
reason: "Missing .pi/tdd/",
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const ctx = makeCtx();
|
|
306
|
+
await handleTddReset(ctx, makeDeps());
|
|
307
|
+
|
|
308
|
+
expect(ctx.notifications).toHaveLength(1);
|
|
309
|
+
expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
|
|
310
|
+
expect(ctx.notifications[0].type).toBe("error");
|
|
311
|
+
});
|
|
302
312
|
});
|