tdd-enforcer 0.2.1 → 0.2.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.
@@ -0,0 +1,302 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { handleTddOn, handleTddOff, handleTddStatus, handleTddReset } from "./index.js";
3
+
4
+ const config = {
5
+ allowedRedPhaseFiles: ["tests/**/*.test.ts"],
6
+ allowedGreenPhaseFiles: ["src/**/*.ts"],
7
+ testCommands: ["npm test"],
8
+ timeoutSeconds: 30,
9
+ };
10
+
11
+ // ── Helpers ─────────────────────────────────────────────────────────────────
12
+
13
+ function makeCtx(dir = "/test") {
14
+ const notifications: Array<{ message: string; type: string }> = [];
15
+ return {
16
+ cwd: dir,
17
+ ui: { notify: (message: string, type: string) => notifications.push({ message, type }) },
18
+ notifications, // stored alongside for easy assertion
19
+ } as any;
20
+ }
21
+
22
+ function captureNotifications(ctx: any): Array<{ message: string; type: string }> {
23
+ return ctx.notifications;
24
+ }
25
+
26
+ // ── handleTddOn ─────────────────────────────────────────────────────────────
27
+
28
+ describe("handleTddOn", () => {
29
+ let mockLoadTddState: ReturnType<typeof vi.fn>;
30
+ let mockSnapshot: ReturnType<typeof vi.fn>;
31
+ let mockSavePhaseState: ReturnType<typeof vi.fn>;
32
+ let mockTddLog: ReturnType<typeof vi.fn>;
33
+
34
+ function makeDeps(overrides = {}) {
35
+ return {
36
+ loadTddState: mockLoadTddState,
37
+ snapshot: mockSnapshot,
38
+ savePhaseState: mockSavePhaseState,
39
+ tddLog: mockTddLog,
40
+ ...overrides,
41
+ };
42
+ }
43
+
44
+ beforeEach(() => {
45
+ mockLoadTddState = vi.fn();
46
+ mockSnapshot = vi.fn().mockReturnValue("hash123");
47
+ mockSavePhaseState = vi.fn();
48
+ mockTddLog = vi.fn();
49
+ });
50
+
51
+ it("enables TDD, takes snapshot, notifies user", async () => {
52
+ mockLoadTddState.mockReturnValue({
53
+ ok: true,
54
+ state: { enabled: false, current: "red" },
55
+ config,
56
+ });
57
+
58
+ const ctx = makeCtx();
59
+ await handleTddOn(ctx, makeDeps());
60
+
61
+ expect(mockSnapshot).toHaveBeenCalledWith("/test", "red");
62
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
63
+ enabled: true,
64
+ current: "red",
65
+ });
66
+ expect(ctx.notifications).toHaveLength(1);
67
+ expect(ctx.notifications[0].message).toContain("TDD enabled");
68
+ expect(ctx.notifications[0].type).toBe("info");
69
+ });
70
+
71
+ it("shows already enabled when TDD already on", async () => {
72
+ mockLoadTddState.mockReturnValue({
73
+ ok: true,
74
+ state: { enabled: true, current: "red" },
75
+ config,
76
+ });
77
+
78
+ const ctx = makeCtx();
79
+ await handleTddOn(ctx, makeDeps());
80
+
81
+ expect(ctx.notifications).toHaveLength(1);
82
+ expect(ctx.notifications[0].message).toContain("already enabled");
83
+ expect(ctx.notifications[0].type).toBe("info");
84
+ expect(mockSnapshot).not.toHaveBeenCalled();
85
+ });
86
+
87
+ it("shows error when setup invalid", async () => {
88
+ mockLoadTddState.mockReturnValue({
89
+ ok: false,
90
+ reason: "Missing .pi/tdd/",
91
+ });
92
+
93
+ const ctx = makeCtx();
94
+ await handleTddOn(ctx, makeDeps());
95
+
96
+ expect(ctx.notifications).toHaveLength(1);
97
+ expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
98
+ expect(ctx.notifications[0].type).toBe("error");
99
+ });
100
+ });
101
+
102
+ // ── handleTddOff ────────────────────────────────────────────────────────────
103
+
104
+ describe("handleTddOff", () => {
105
+ let mockLoadTddState: ReturnType<typeof vi.fn>;
106
+ let mockSavePhaseState: ReturnType<typeof vi.fn>;
107
+ let mockTddLog: ReturnType<typeof vi.fn>;
108
+
109
+ function makeDeps(overrides = {}) {
110
+ return {
111
+ loadTddState: mockLoadTddState,
112
+ savePhaseState: mockSavePhaseState,
113
+ tddLog: mockTddLog,
114
+ ...overrides,
115
+ };
116
+ }
117
+
118
+ beforeEach(() => {
119
+ mockLoadTddState = vi.fn();
120
+ mockSavePhaseState = vi.fn();
121
+ mockTddLog = vi.fn();
122
+ });
123
+
124
+ it("disables TDD, notifies user", async () => {
125
+ mockLoadTddState.mockReturnValue({
126
+ ok: true,
127
+ state: { enabled: true, current: "red" },
128
+ config,
129
+ });
130
+
131
+ const ctx = makeCtx();
132
+ await handleTddOff(ctx, makeDeps());
133
+
134
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
135
+ enabled: false,
136
+ current: "red",
137
+ });
138
+ expect(ctx.notifications).toHaveLength(1);
139
+ expect(ctx.notifications[0].message).toContain("disabled");
140
+ expect(ctx.notifications[0].type).toBe("info");
141
+ });
142
+
143
+ it("shows already disabled when TDD already off", async () => {
144
+ mockLoadTddState.mockReturnValue({
145
+ ok: true,
146
+ state: { enabled: false, current: "red" },
147
+ config,
148
+ });
149
+
150
+ const ctx = makeCtx();
151
+ await handleTddOff(ctx, makeDeps());
152
+
153
+ expect(ctx.notifications).toHaveLength(1);
154
+ expect(ctx.notifications[0].message).toContain("already disabled");
155
+ expect(ctx.notifications[0].type).toBe("info");
156
+ expect(mockSavePhaseState).not.toHaveBeenCalled();
157
+ });
158
+
159
+ it("shows error when setup invalid", async () => {
160
+ mockLoadTddState.mockReturnValue({
161
+ ok: false,
162
+ reason: "Missing .pi/tdd/",
163
+ });
164
+
165
+ const ctx = makeCtx();
166
+ await handleTddOff(ctx, makeDeps());
167
+
168
+ expect(ctx.notifications).toHaveLength(1);
169
+ expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
170
+ expect(ctx.notifications[0].type).toBe("error");
171
+ });
172
+ });
173
+
174
+ // ── handleTddStatus ─────────────────────────────────────────────────────────
175
+
176
+ describe("handleTddStatus", () => {
177
+ let mockLoadTddState: ReturnType<typeof vi.fn>;
178
+ let mockTddLog: ReturnType<typeof vi.fn>;
179
+
180
+ function makeDeps(overrides = {}) {
181
+ return {
182
+ loadTddState: mockLoadTddState,
183
+ tddLog: mockTddLog,
184
+ ...overrides,
185
+ };
186
+ }
187
+
188
+ beforeEach(() => {
189
+ mockLoadTddState = vi.fn();
190
+ mockTddLog = vi.fn();
191
+ });
192
+
193
+ it("shows status when TDD enabled", async () => {
194
+ mockLoadTddState.mockReturnValue({
195
+ ok: true,
196
+ state: { enabled: true, current: "green" },
197
+ config,
198
+ });
199
+
200
+ const ctx = makeCtx();
201
+ await handleTddStatus(ctx, makeDeps());
202
+
203
+ expect(ctx.notifications).toHaveLength(1);
204
+ expect(ctx.notifications[0].message).toContain("enabled");
205
+ expect(ctx.notifications[0].message).toContain("GREEN");
206
+ expect(ctx.notifications[0].type).toBe("info");
207
+ });
208
+
209
+ it("shows status when TDD disabled", async () => {
210
+ mockLoadTddState.mockReturnValue({
211
+ ok: true,
212
+ state: { enabled: false, current: "red" },
213
+ config,
214
+ });
215
+
216
+ const ctx = makeCtx();
217
+ await handleTddStatus(ctx, makeDeps());
218
+
219
+ expect(ctx.notifications).toHaveLength(1);
220
+ expect(ctx.notifications[0].message).toContain("disabled");
221
+ expect(ctx.notifications[0].message).toContain("RED");
222
+ expect(ctx.notifications[0].type).toBe("info");
223
+ });
224
+
225
+ it("shows error when setup invalid", async () => {
226
+ mockLoadTddState.mockReturnValue({
227
+ ok: false,
228
+ reason: "Missing .pi/tdd/",
229
+ });
230
+
231
+ const ctx = makeCtx();
232
+ await handleTddStatus(ctx, makeDeps());
233
+
234
+ expect(ctx.notifications).toHaveLength(1);
235
+ expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
236
+ expect(ctx.notifications[0].type).toBe("error");
237
+ });
238
+ });
239
+
240
+ // ── handleTddReset ──────────────────────────────────────────────────────────
241
+
242
+ describe("handleTddReset", () => {
243
+ let mockLoadTddState: ReturnType<typeof vi.fn>;
244
+ let mockResetGit: ReturnType<typeof vi.fn>;
245
+ let mockSnapshot: ReturnType<typeof vi.fn>;
246
+ let mockSavePhaseState: ReturnType<typeof vi.fn>;
247
+ let mockTddLog: ReturnType<typeof vi.fn>;
248
+
249
+ function makeDeps(overrides = {}) {
250
+ return {
251
+ loadTddState: mockLoadTddState,
252
+ resetGit: mockResetGit,
253
+ snapshot: mockSnapshot,
254
+ savePhaseState: mockSavePhaseState,
255
+ tddLog: mockTddLog,
256
+ ...overrides,
257
+ };
258
+ }
259
+
260
+ beforeEach(() => {
261
+ mockLoadTddState = vi.fn();
262
+ mockResetGit = vi.fn();
263
+ mockSnapshot = vi.fn().mockReturnValue("hash123");
264
+ mockSavePhaseState = vi.fn();
265
+ mockTddLog = vi.fn();
266
+ });
267
+
268
+ it("nukes git, re-inits, snapshots, resets state to RED disabled", async () => {
269
+ mockLoadTddState.mockReturnValue({
270
+ ok: true,
271
+ state: { enabled: true, current: "green" },
272
+ config,
273
+ });
274
+
275
+ const ctx = makeCtx();
276
+ await handleTddReset(ctx, makeDeps());
277
+
278
+ expect(mockResetGit).toHaveBeenCalledWith("/test");
279
+ expect(mockSnapshot).toHaveBeenCalledWith("/test", "red");
280
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
281
+ enabled: false,
282
+ current: "red",
283
+ });
284
+ expect(ctx.notifications).toHaveLength(1);
285
+ expect(ctx.notifications[0].message).toContain("reset");
286
+ expect(ctx.notifications[0].type).toBe("warning");
287
+ });
288
+
289
+ it("shows error when setup invalid", async () => {
290
+ mockLoadTddState.mockReturnValue({
291
+ ok: false,
292
+ reason: "Missing .pi/tdd/",
293
+ });
294
+
295
+ const ctx = makeCtx();
296
+ await handleTddReset(ctx, makeDeps());
297
+
298
+ expect(ctx.notifications).toHaveLength(1);
299
+ expect(ctx.notifications[0].message).toContain("Missing .pi/tdd/");
300
+ expect(ctx.notifications[0].type).toBe("error");
301
+ });
302
+ });
@@ -1,5 +1,4 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { existsSync } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import { savePhaseState, resetGit, snapshot } from "../../engine/index.js";
5
4
  import { registerTools } from "./tools.js";
@@ -7,157 +6,215 @@ import { registerHooks } from "./hooks.js";
7
6
  import { loadTddState } from "./helpers.js";
8
7
  import { tddLog } from "./log.js";
9
8
 
9
+ export async function handleTddOn(
10
+ ctx: ExtensionContext,
11
+ deps: {
12
+ loadTddState: typeof loadTddState;
13
+ snapshot: typeof snapshot;
14
+ savePhaseState: typeof savePhaseState;
15
+ tddLog: typeof tddLog;
16
+ } = {
17
+ loadTddState,
18
+ snapshot,
19
+ savePhaseState,
20
+ tddLog,
21
+ },
22
+ ): Promise<void> {
23
+ const root = ctx.cwd;
24
+ const tddDir = join(root, ".pi", "tdd");
25
+
26
+ deps.tddLog(tddDir, "INFO", "tdd:on: starting");
27
+
28
+ const setup = deps.loadTddState(root);
29
+ if (!setup.ok) {
30
+ deps.tddLog(tddDir, "WARN", "tdd:on: setup invalid", { reason: setup.reason });
31
+ ctx.ui.notify(setup.reason, "error");
32
+ return;
33
+ }
34
+
35
+ const { state } = setup;
36
+
37
+ if (state.enabled) {
38
+ deps.tddLog(tddDir, "INFO", "tdd:on: already enabled", {
39
+ phase: state.current,
40
+ });
41
+ ctx.ui.notify(`TDD already enabled — ${state.current.toUpperCase()} phase`, "info");
42
+ return;
43
+ }
44
+
45
+ // Snapshot working tree so stale baseline doesn't nuke user changes
46
+ deps.snapshot(root, state.current);
47
+ deps.tddLog(tddDir, "INFO", "tdd:on: snapshot taken", {
48
+ phase: state.current,
49
+ });
50
+
51
+ state.enabled = true;
52
+ deps.savePhaseState(root, state);
53
+ deps.tddLog(tddDir, "INFO", "tdd:on: enabled", {
54
+ phase: state.current,
55
+ });
56
+ ctx.ui.notify(`TDD enabled — ${state.current.toUpperCase()} phase`, "info");
57
+ }
58
+
59
+ export async function handleTddOff(
60
+ ctx: ExtensionContext,
61
+ deps: {
62
+ loadTddState: typeof loadTddState;
63
+ savePhaseState: typeof savePhaseState;
64
+ tddLog: typeof tddLog;
65
+ } = {
66
+ loadTddState,
67
+ savePhaseState,
68
+ tddLog,
69
+ },
70
+ ): Promise<void> {
71
+ const root = ctx.cwd;
72
+ const tddDir = join(root, ".pi", "tdd");
73
+
74
+ const setup = deps.loadTddState(root);
75
+ if (!setup.ok) {
76
+ deps.tddLog(tddDir, "WARN", "tdd:off: setup invalid", { reason: setup.reason });
77
+ ctx.ui.notify(setup.reason, "error");
78
+ return;
79
+ }
80
+
81
+ const { state } = setup;
82
+
83
+ if (!state.enabled) {
84
+ deps.tddLog(tddDir, "INFO", "tdd:off: already disabled");
85
+ ctx.ui.notify("TDD already disabled", "info");
86
+ return;
87
+ }
88
+
89
+ state.enabled = false;
90
+ deps.savePhaseState(root, state);
91
+ deps.tddLog(tddDir, "INFO", "tdd:off: disabled", {
92
+ was: state.current,
93
+ });
94
+ ctx.ui.notify("TDD disabled", "info");
95
+ }
96
+
97
+ export async function handleTddStatus(
98
+ ctx: ExtensionContext,
99
+ deps: {
100
+ loadTddState: typeof loadTddState;
101
+ tddLog: typeof tddLog;
102
+ } = {
103
+ loadTddState,
104
+ tddLog,
105
+ },
106
+ ): Promise<void> {
107
+ const root = ctx.cwd;
108
+ const tddDir = join(root, ".pi", "tdd");
109
+ const result = deps.loadTddState(root);
110
+
111
+ if (!result.ok) {
112
+ deps.tddLog(tddDir, "WARN", "tdd:status: setup invalid", {
113
+ reason: result.reason,
114
+ });
115
+ ctx.ui.notify(`TDD: ${result.reason}`, "error");
116
+ return;
117
+ }
118
+
119
+ const { state, config } = result;
120
+ const enabledStr = state.enabled ? "enabled" : "disabled";
121
+ const phaseStr = state.current.toUpperCase();
122
+ const redGlobs = config.allowedRedPhaseFiles.join(", ") || "(none)";
123
+ const greenGlobs = config.allowedGreenPhaseFiles.join(", ") || "(none)";
124
+ const commands = config.testCommands.join(", ") || "(none)";
125
+
126
+ deps.tddLog(tddDir, "INFO", "tdd:status: queried", {
127
+ enabled: state.enabled,
128
+ phase: state.current,
129
+ });
130
+
131
+ ctx.ui.notify(
132
+ `TDD enforcer ${enabledStr}\n` +
133
+ `Current phase: ${phaseStr}\n` +
134
+ `Test files: ${redGlobs}\n` +
135
+ `Impl files: ${greenGlobs}\n` +
136
+ `Test commands: ${commands}`,
137
+ "info",
138
+ );
139
+ }
140
+
141
+ export async function handleTddReset(
142
+ ctx: ExtensionContext,
143
+ deps: {
144
+ loadTddState: typeof loadTddState;
145
+ resetGit: typeof resetGit;
146
+ snapshot: typeof snapshot;
147
+ savePhaseState: typeof savePhaseState;
148
+ tddLog: typeof tddLog;
149
+ } = {
150
+ loadTddState,
151
+ resetGit,
152
+ snapshot,
153
+ savePhaseState,
154
+ tddLog,
155
+ },
156
+ ): Promise<void> {
157
+ const root = ctx.cwd;
158
+ const tddDir = join(root, ".pi", "tdd");
159
+
160
+ deps.tddLog(tddDir, "INFO", "tdd:reset: starting");
161
+
162
+ const setup = deps.loadTddState(root);
163
+ if (!setup.ok) {
164
+ deps.tddLog(tddDir, "WARN", "tdd:reset: setup invalid", { reason: setup.reason });
165
+ ctx.ui.notify(setup.reason, "error");
166
+ return;
167
+ }
168
+
169
+ // Nuke git history and re-init
170
+ try {
171
+ deps.resetGit(root);
172
+ deps.tddLog(tddDir, "INFO", "tdd:reset: git reset and re-initialised");
173
+ } catch (e) {
174
+ deps.tddLog(tddDir, "ERROR", "tdd:reset: git reset failed", {
175
+ error: (e as Error).message,
176
+ });
177
+ ctx.ui.notify("Failed to reset private git repo.", "error");
178
+ return;
179
+ }
180
+
181
+ // Snapshot current working tree
182
+ deps.snapshot(root, "red");
183
+ deps.tddLog(tddDir, "INFO", "tdd:reset: snapshot taken");
184
+
185
+ // Reset state to RED (disabled, user must run /tdd:on)
186
+ deps.savePhaseState(root, { enabled: false, current: "red" });
187
+ deps.tddLog(tddDir, "INFO", "tdd:reset: complete");
188
+
189
+ ctx.ui.notify(
190
+ "TDD snapshot history reset. Run /tdd:on to re-enable enforcement.",
191
+ "warning",
192
+ );
193
+ }
194
+
10
195
  export default function (pi: ExtensionAPI) {
196
+ const defaultDeps = { loadTddState, snapshot, savePhaseState, tddLog, resetGit };
197
+
11
198
  pi.registerCommand("tdd:on", {
12
199
  description: "Enable TDD enforcement",
13
- handler: async (_args: string, ctx: ExtensionContext) => {
14
- const root = ctx.cwd;
15
- const tddDir = join(root, ".pi", "tdd");
16
-
17
- tddLog(tddDir, "INFO", "tdd:on: starting");
18
-
19
- const setup = loadTddState(root);
20
- if (!setup.ok) {
21
- tddLog(tddDir, "WARN", "tdd:on: setup invalid", { reason: setup.reason });
22
- ctx.ui.notify(setup.reason, "error");
23
- return;
24
- }
25
-
26
- const { state } = setup;
27
-
28
- if (state.enabled) {
29
- tddLog(tddDir, "INFO", "tdd:on: already enabled", {
30
- phase: state.current,
31
- });
32
- ctx.ui.notify(`TDD already enabled — ${state.current.toUpperCase()} phase`, "info");
33
- return;
34
- }
35
-
36
- // Snapshot working tree so stale baseline doesn't nuke user changes
37
- snapshot(root, state.current);
38
- tddLog(tddDir, "INFO", "tdd:on: snapshot taken", {
39
- phase: state.current,
40
- });
41
-
42
- state.enabled = true;
43
- savePhaseState(root, state);
44
- tddLog(tddDir, "INFO", "tdd:on: enabled", {
45
- phase: state.current,
46
- });
47
- ctx.ui.notify(`TDD enabled — ${state.current.toUpperCase()} phase`, "info");
48
- },
200
+ handler: (_args: string, ctx: ExtensionContext) => handleTddOn(ctx, defaultDeps),
49
201
  });
50
202
 
51
203
  pi.registerCommand("tdd:off", {
52
204
  description: "Disable TDD enforcement",
53
- handler: async (_args: string, ctx: ExtensionContext) => {
54
- const root = ctx.cwd;
55
- const tddDir = join(root, ".pi", "tdd");
56
-
57
- const setup = loadTddState(root);
58
- if (!setup.ok) {
59
- tddLog(tddDir, "WARN", "tdd:off: setup invalid", { reason: setup.reason });
60
- ctx.ui.notify(setup.reason, "error");
61
- return;
62
- }
63
-
64
- const { state } = setup;
65
-
66
- if (!state.enabled) {
67
- tddLog(tddDir, "INFO", "tdd:off: already disabled");
68
- ctx.ui.notify("TDD already disabled", "info");
69
- return;
70
- }
71
-
72
- state.enabled = false;
73
- savePhaseState(root, state);
74
- tddLog(tddDir, "INFO", "tdd:off: disabled", {
75
- was: state.current,
76
- });
77
- ctx.ui.notify("TDD disabled", "info");
78
- },
205
+ handler: (_args: string, ctx: ExtensionContext) => handleTddOff(ctx, defaultDeps),
79
206
  });
80
207
 
81
208
  pi.registerCommand("tdd:status", {
82
209
  description: "Show TDD enforcement status",
83
- handler: async (_args: string, ctx: ExtensionContext) => {
84
- const root = ctx.cwd;
85
- const tddDir = join(root, ".pi", "tdd");
86
- const result = loadTddState(root);
87
-
88
- if (!result.ok) {
89
- tddLog(tddDir, "WARN", "tdd:status: setup invalid", {
90
- reason: result.reason,
91
- });
92
- ctx.ui.notify(`TDD: ${result.reason}`, "error");
93
- return;
94
- }
95
-
96
- const { state, config } = result;
97
- const enabledStr = state.enabled ? "enabled" : "disabled";
98
- const phaseStr = state.current.toUpperCase();
99
- const redGlobs = config.allowedRedPhaseFiles.join(", ") || "(none)";
100
- const greenGlobs = config.allowedGreenPhaseFiles.join(", ") || "(none)";
101
- const commands = config.testCommands.join(", ") || "(none)";
102
-
103
- tddLog(tddDir, "INFO", "tdd:status: queried", {
104
- enabled: state.enabled,
105
- phase: state.current,
106
- });
107
-
108
- ctx.ui.notify(
109
- `TDD enforcer ${enabledStr}\n` +
110
- `Current phase: ${phaseStr}\n` +
111
- `Test files: ${redGlobs}\n` +
112
- `Impl files: ${greenGlobs}\n` +
113
- `Test commands: ${commands}`,
114
- "info"
115
- );
116
- },
210
+ handler: (_args: string, ctx: ExtensionContext) => handleTddStatus(ctx, defaultDeps),
117
211
  });
118
212
 
119
213
  pi.registerCommand("tdd:reset", {
120
214
  description:
121
215
  "WARNING: Destroys ALL TDD snapshot history and resets to RED phase. " +
122
216
  "Working tree is preserved. Run /tdd:on to re-enable after reset.",
123
- handler: async (_args: string, ctx: ExtensionContext) => {
124
- const root = ctx.cwd;
125
- const tddDir = join(root, ".pi", "tdd");
126
-
127
- tddLog(tddDir, "INFO", "tdd:reset: starting");
128
-
129
- const setup = loadTddState(root);
130
- if (!setup.ok) {
131
- tddLog(tddDir, "WARN", "tdd:reset: setup invalid", { reason: setup.reason });
132
- ctx.ui.notify(setup.reason, "error");
133
- return;
134
- }
135
-
136
- // Nuke git history and re-init
137
- try {
138
- resetGit(root);
139
- tddLog(tddDir, "INFO", "tdd:reset: git reset and re-initialised");
140
- } catch (e) {
141
- tddLog(tddDir, "ERROR", "tdd:reset: git reset failed", {
142
- error: (e as Error).message,
143
- });
144
- ctx.ui.notify("Failed to reset private git repo.", "error");
145
- return;
146
- }
147
-
148
- // Snapshot current working tree
149
- snapshot(root, "red");
150
- tddLog(tddDir, "INFO", "tdd:reset: snapshot taken");
151
-
152
- // Reset state to RED (disabled, user must run /tdd:on)
153
- savePhaseState(root, { enabled: false, current: "red" });
154
- tddLog(tddDir, "INFO", "tdd:reset: complete");
155
-
156
- ctx.ui.notify(
157
- "TDD snapshot history reset. Run /tdd:on to re-enable enforcement.",
158
- "warning",
159
- );
160
- },
217
+ handler: (_args: string, ctx: ExtensionContext) => handleTddReset(ctx, { ...defaultDeps, resetGit }),
161
218
  });
162
219
 
163
220
  registerTools(pi);