tdd-enforcer 0.2.9 → 0.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/adapters/pi/index.test.ts +11 -19
- package/adapters/pi/index.ts +295 -294
- package/package.json +30 -30
|
@@ -430,10 +430,8 @@ describe("handleBeforeAgentStart", () => {
|
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
function makeEvent(): {
|
|
434
|
-
|
|
435
|
-
} {
|
|
436
|
-
return { systemPromptOptions: { promptGuidelines: [] } };
|
|
433
|
+
function makeEvent(): { systemPrompt: string } {
|
|
434
|
+
return { systemPrompt: "" };
|
|
437
435
|
}
|
|
438
436
|
|
|
439
437
|
beforeEach(() => {
|
|
@@ -441,7 +439,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
441
439
|
mockLoadTddState = vi.fn();
|
|
442
440
|
});
|
|
443
441
|
|
|
444
|
-
it("
|
|
442
|
+
it("appends TDD instructions when TDD enabled", async () => {
|
|
445
443
|
mockLoadTddState.mockReturnValue({
|
|
446
444
|
ok: true,
|
|
447
445
|
state: { enabled: true, current: "red" },
|
|
@@ -458,19 +456,13 @@ describe("handleBeforeAgentStart", () => {
|
|
|
458
456
|
{ cwd: "/test" } as any,
|
|
459
457
|
makeDeps(),
|
|
460
458
|
);
|
|
461
|
-
expect(event.
|
|
462
|
-
expect(event.
|
|
463
|
-
|
|
464
|
-
);
|
|
465
|
-
expect(event.systemPromptOptions.promptGuidelines[1]).toContain(
|
|
466
|
-
"next_tdd_phase",
|
|
467
|
-
);
|
|
468
|
-
expect(event.systemPromptOptions.promptGuidelines[2]).toContain(
|
|
469
|
-
"cycle so reverting is cheap",
|
|
470
|
-
);
|
|
459
|
+
expect(event.systemPrompt).toContain("TDD enforcement");
|
|
460
|
+
expect(event.systemPrompt).toContain("locked files will be blocked");
|
|
461
|
+
expect(event.systemPrompt).toContain("cycle so reverting is cheap");
|
|
462
|
+
expect(event.systemPrompt).not.toContain("next_tdd_phase");
|
|
471
463
|
});
|
|
472
464
|
|
|
473
|
-
it("does not
|
|
465
|
+
it("does not modify systemPrompt when TDD not setup", async () => {
|
|
474
466
|
mockLoadTddState.mockReturnValue({
|
|
475
467
|
ok: false,
|
|
476
468
|
reason: "Missing .pi/tdd/",
|
|
@@ -481,10 +473,10 @@ describe("handleBeforeAgentStart", () => {
|
|
|
481
473
|
{ cwd: "/test" } as any,
|
|
482
474
|
makeDeps(),
|
|
483
475
|
);
|
|
484
|
-
expect(event.
|
|
476
|
+
expect(event.systemPrompt).toBe("");
|
|
485
477
|
});
|
|
486
478
|
|
|
487
|
-
it("
|
|
479
|
+
it("appends disabled message when TDD was disabled", async () => {
|
|
488
480
|
mockLoadTddState.mockReturnValue({
|
|
489
481
|
ok: true,
|
|
490
482
|
state: { enabled: false, current: "red" },
|
|
@@ -501,6 +493,6 @@ describe("handleBeforeAgentStart", () => {
|
|
|
501
493
|
{ cwd: "/test" } as any,
|
|
502
494
|
makeDeps(),
|
|
503
495
|
);
|
|
504
|
-
expect(event.
|
|
496
|
+
expect(event.systemPrompt).toContain("was disabled");
|
|
505
497
|
});
|
|
506
498
|
});
|
package/adapters/pi/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import type {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
ExtensionAPI,
|
|
4
|
+
ExtensionContext,
|
|
5
5
|
} from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { resetGit, savePhaseState, snapshot } from "../../engine/index.js";
|
|
7
7
|
import { loadTddState } from "./helpers.js";
|
|
@@ -10,320 +10,321 @@ import { tddLog } from "./log.js";
|
|
|
10
10
|
import { registerTools } from "./tools.js";
|
|
11
11
|
|
|
12
12
|
export async function handleTddOn(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
ctx: ExtensionContext,
|
|
14
|
+
deps: {
|
|
15
|
+
loadTddState: typeof loadTddState;
|
|
16
|
+
snapshot: typeof snapshot;
|
|
17
|
+
savePhaseState: typeof savePhaseState;
|
|
18
|
+
tddLog: typeof tddLog;
|
|
19
|
+
} = {
|
|
20
|
+
loadTddState,
|
|
21
|
+
snapshot,
|
|
22
|
+
savePhaseState,
|
|
23
|
+
tddLog,
|
|
24
|
+
},
|
|
25
25
|
): Promise<void> {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
26
|
+
const root = ctx.cwd;
|
|
27
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
28
|
+
|
|
29
|
+
deps.tddLog(tddDir, "INFO", "tdd:on: starting");
|
|
30
|
+
|
|
31
|
+
const setup = deps.loadTddState(root);
|
|
32
|
+
if (!setup.ok) {
|
|
33
|
+
deps.tddLog(tddDir, "WARN", "tdd:on: setup invalid", {
|
|
34
|
+
reason: setup.reason,
|
|
35
|
+
});
|
|
36
|
+
ctx.ui.notify(setup.reason, "error");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { state } = setup;
|
|
41
|
+
|
|
42
|
+
if (state.enabled) {
|
|
43
|
+
deps.tddLog(tddDir, "INFO", "tdd:on: already enabled", {
|
|
44
|
+
phase: state.current,
|
|
45
|
+
});
|
|
46
|
+
ctx.ui.notify(
|
|
47
|
+
`TDD already enabled — ${state.current.toUpperCase()} phase`,
|
|
48
|
+
"info",
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Snapshot working tree so stale baseline doesn't nuke user changes
|
|
54
|
+
deps.snapshot(root, state.current);
|
|
55
|
+
deps.tddLog(tddDir, "INFO", "tdd:on: snapshot taken", {
|
|
56
|
+
phase: state.current,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
state.enabled = true;
|
|
60
|
+
deps.savePhaseState(root, state);
|
|
61
|
+
deps.tddLog(tddDir, "INFO", "tdd:on: enabled", {
|
|
62
|
+
phase: state.current,
|
|
63
|
+
});
|
|
64
|
+
ctx.ui.notify(`TDD enabled — ${state.current.toUpperCase()} phase`, "info");
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export async function handleTddOff(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
ctx: ExtensionContext,
|
|
69
|
+
deps: {
|
|
70
|
+
loadTddState: typeof loadTddState;
|
|
71
|
+
savePhaseState: typeof savePhaseState;
|
|
72
|
+
tddLog: typeof tddLog;
|
|
73
|
+
} = {
|
|
74
|
+
loadTddState,
|
|
75
|
+
savePhaseState,
|
|
76
|
+
tddLog,
|
|
77
|
+
},
|
|
78
78
|
): Promise<void> {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
79
|
+
const root = ctx.cwd;
|
|
80
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
81
|
+
|
|
82
|
+
const setup = deps.loadTddState(root);
|
|
83
|
+
if (!setup.ok) {
|
|
84
|
+
deps.tddLog(tddDir, "WARN", "tdd:off: setup invalid", {
|
|
85
|
+
reason: setup.reason,
|
|
86
|
+
});
|
|
87
|
+
ctx.ui.notify(setup.reason, "error");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { state } = setup;
|
|
92
|
+
|
|
93
|
+
if (!state.enabled) {
|
|
94
|
+
deps.tddLog(tddDir, "INFO", "tdd:off: already disabled");
|
|
95
|
+
ctx.ui.notify("TDD already disabled", "info");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
state.enabled = false;
|
|
100
|
+
deps.savePhaseState(root, state);
|
|
101
|
+
deps.tddLog(tddDir, "INFO", "tdd:off: disabled", {
|
|
102
|
+
was: state.current,
|
|
103
|
+
});
|
|
104
|
+
ctx.ui.notify("TDD disabled", "info");
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export async function handleTddStatus(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
ctx: ExtensionContext,
|
|
109
|
+
deps: {
|
|
110
|
+
loadTddState: typeof loadTddState;
|
|
111
|
+
tddLog: typeof tddLog;
|
|
112
|
+
} = {
|
|
113
|
+
loadTddState,
|
|
114
|
+
tddLog,
|
|
115
|
+
},
|
|
116
116
|
): Promise<void> {
|
|
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
|
-
|
|
117
|
+
const root = ctx.cwd;
|
|
118
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
119
|
+
const result = deps.loadTddState(root);
|
|
120
|
+
|
|
121
|
+
if (!result.ok) {
|
|
122
|
+
deps.tddLog(tddDir, "WARN", "tdd:status: setup invalid", {
|
|
123
|
+
reason: result.reason,
|
|
124
|
+
});
|
|
125
|
+
ctx.ui.notify(`TDD: ${result.reason}`, "error");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { state, config } = result;
|
|
130
|
+
const enabledStr = state.enabled ? "enabled" : "disabled";
|
|
131
|
+
const phaseStr = state.current.toUpperCase();
|
|
132
|
+
const redBlk = config.blockedInRed.join(", ") || "(none)";
|
|
133
|
+
const greenBlk = config.blockedInGreen.join(", ") || "(none)";
|
|
134
|
+
const commands = config.testCommands.join(", ") || "(none)";
|
|
135
|
+
|
|
136
|
+
deps.tddLog(tddDir, "INFO", "tdd:status: queried", {
|
|
137
|
+
enabled: state.enabled,
|
|
138
|
+
phase: state.current,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
ctx.ui.notify(
|
|
142
|
+
`TDD enforcer ${enabledStr}\n` +
|
|
143
|
+
`Current phase: ${phaseStr}\n` +
|
|
144
|
+
`Blocked in RED: ${redBlk}\n` +
|
|
145
|
+
`Blocked in GREEN: ${greenBlk}\n` +
|
|
146
|
+
`Test commands: ${commands}`,
|
|
147
|
+
"info",
|
|
148
|
+
);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
export async function handleBeforeAgentStart(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
loadTddState: typeof loadTddState;
|
|
158
|
-
},
|
|
152
|
+
event: { systemPrompt: string },
|
|
153
|
+
ctx: { cwd: string },
|
|
154
|
+
deps: {
|
|
155
|
+
loadTddState: typeof loadTddState;
|
|
156
|
+
},
|
|
159
157
|
): Promise<void> {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
const tdd = deps.loadTddState(ctx.cwd);
|
|
159
|
+
if (!tdd.ok) return;
|
|
160
|
+
|
|
161
|
+
if (tdd.state.enabled) {
|
|
162
|
+
event.systemPrompt +=
|
|
163
|
+
"\n\nYou are working under TDD enforcement. Each phase restricts which files you can modify — locked files will be blocked automatically.\n" +
|
|
164
|
+
"Minimise the scope of each TDD cycle so reverting is cheap.";
|
|
165
|
+
} else {
|
|
166
|
+
event.systemPrompt +=
|
|
167
|
+
"\n\nTDD enforcement was disabled. File restrictions are no longer enforced.";
|
|
168
|
+
}
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
export async function handleTddJump(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
phase: "red" | "green" | "refactor",
|
|
173
|
+
ctx: ExtensionContext,
|
|
174
|
+
deps: {
|
|
175
|
+
loadTddState: typeof loadTddState;
|
|
176
|
+
snapshot: typeof snapshot;
|
|
177
|
+
savePhaseState: typeof savePhaseState;
|
|
178
|
+
tddLog: typeof tddLog;
|
|
179
|
+
} = {
|
|
180
|
+
loadTddState,
|
|
181
|
+
snapshot,
|
|
182
|
+
savePhaseState,
|
|
183
|
+
tddLog,
|
|
184
|
+
},
|
|
184
185
|
): Promise<void> {
|
|
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
|
-
|
|
186
|
+
const root = ctx.cwd;
|
|
187
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
188
|
+
|
|
189
|
+
const setup = deps.loadTddState(root);
|
|
190
|
+
if (!setup.ok) {
|
|
191
|
+
deps.tddLog(tddDir, "WARN", `tdd:${phase}: setup invalid`, {
|
|
192
|
+
reason: setup.reason,
|
|
193
|
+
});
|
|
194
|
+
ctx.ui.notify(setup.reason, "error");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { state } = setup;
|
|
199
|
+
|
|
200
|
+
if (state.current === phase) {
|
|
201
|
+
deps.tddLog(tddDir, "INFO", `tdd:${phase}: already in ${phase}`, {
|
|
202
|
+
phase,
|
|
203
|
+
});
|
|
204
|
+
ctx.ui.notify(`TDD: already in ${phase.toUpperCase()} phase.`, "info");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Snapshot the current phase's work before jumping
|
|
209
|
+
deps.snapshot(root, state.current);
|
|
210
|
+
deps.tddLog(tddDir, "INFO", `tdd:${phase}: snapshot taken`, {
|
|
211
|
+
from: state.current,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Auto-enable if disabled, set phase
|
|
215
|
+
state.enabled = true;
|
|
216
|
+
state.current = phase;
|
|
217
|
+
deps.savePhaseState(root, state);
|
|
218
|
+
|
|
219
|
+
deps.tddLog(tddDir, "INFO", `tdd:${phase}: jumped`);
|
|
220
|
+
ctx.ui.notify(`Skipped to ${phase.toUpperCase()} phase.`, "info");
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
export async function handleTddReset(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
224
|
+
ctx: ExtensionContext,
|
|
225
|
+
deps: {
|
|
226
|
+
loadTddState: typeof loadTddState;
|
|
227
|
+
resetGit: typeof resetGit;
|
|
228
|
+
snapshot: typeof snapshot;
|
|
229
|
+
savePhaseState: typeof savePhaseState;
|
|
230
|
+
tddLog: typeof tddLog;
|
|
231
|
+
} = {
|
|
232
|
+
loadTddState,
|
|
233
|
+
resetGit,
|
|
234
|
+
snapshot,
|
|
235
|
+
savePhaseState,
|
|
236
|
+
tddLog,
|
|
237
|
+
},
|
|
237
238
|
): Promise<void> {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
239
|
+
const root = ctx.cwd;
|
|
240
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
241
|
+
|
|
242
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: starting");
|
|
243
|
+
|
|
244
|
+
const setup = deps.loadTddState(root);
|
|
245
|
+
if (!setup.ok) {
|
|
246
|
+
deps.tddLog(tddDir, "WARN", "tdd:reset: setup invalid", {
|
|
247
|
+
reason: setup.reason,
|
|
248
|
+
});
|
|
249
|
+
ctx.ui.notify(setup.reason, "error");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Nuke git history and re-init
|
|
254
|
+
try {
|
|
255
|
+
deps.resetGit(root);
|
|
256
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: git reset and re-initialised");
|
|
257
|
+
} catch (e) {
|
|
258
|
+
deps.tddLog(tddDir, "ERROR", "tdd:reset: git reset failed", {
|
|
259
|
+
error: (e as Error).message,
|
|
260
|
+
});
|
|
261
|
+
ctx.ui.notify("Failed to reset private git repo.", "error");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Snapshot current working tree
|
|
266
|
+
deps.snapshot(root, "red");
|
|
267
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: snapshot taken");
|
|
268
|
+
|
|
269
|
+
// Reset state to RED (disabled, user must run /tdd:on)
|
|
270
|
+
deps.savePhaseState(root, { enabled: false, current: "red" });
|
|
271
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: complete");
|
|
272
|
+
|
|
273
|
+
ctx.ui.notify(
|
|
274
|
+
"TDD snapshot history reset. Run /tdd:on to re-enable enforcement.",
|
|
275
|
+
"warning",
|
|
276
|
+
);
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
export default function (pi: ExtensionAPI) {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
280
|
+
const defaultDeps = {
|
|
281
|
+
loadTddState,
|
|
282
|
+
snapshot,
|
|
283
|
+
savePhaseState,
|
|
284
|
+
tddLog,
|
|
285
|
+
resetGit,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
pi.registerCommand("tdd:on", {
|
|
289
|
+
description: "Enable TDD enforcement",
|
|
290
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
291
|
+
handleTddOn(ctx, defaultDeps),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
pi.registerCommand("tdd:off", {
|
|
295
|
+
description: "Disable TDD enforcement",
|
|
296
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
297
|
+
handleTddOff(ctx, defaultDeps),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
pi.registerCommand("tdd:status", {
|
|
301
|
+
description: "Show TDD enforcement status",
|
|
302
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
303
|
+
handleTddStatus(ctx, defaultDeps),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
pi.registerCommand("tdd:reset", {
|
|
307
|
+
description:
|
|
308
|
+
"WARNING: Destroys ALL TDD snapshot history and resets to RED phase. " +
|
|
309
|
+
"Working tree is preserved. Run /tdd:on to re-enable after reset.",
|
|
310
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
311
|
+
handleTddReset(ctx, { ...defaultDeps, resetGit }),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
for (const phase of ["red", "green", "refactor"] as const) {
|
|
315
|
+
pi.registerCommand(`tdd:${phase}`, {
|
|
316
|
+
description:
|
|
317
|
+
`Skip to ${phase.toUpperCase()} phase. ` +
|
|
318
|
+
"Snapshot working tree, auto-enable TDD, set phase. No gate checks.",
|
|
319
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
320
|
+
handleTddJump(phase, ctx, defaultDeps),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
registerTools(pi);
|
|
325
|
+
registerHooks(pi);
|
|
326
|
+
|
|
327
|
+
pi.on("before_agent_start", (event, ctx) =>
|
|
328
|
+
handleBeforeAgentStart(event, ctx, { loadTddState }),
|
|
329
|
+
);
|
|
329
330
|
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
2
|
+
"name": "tdd-enforcer",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"fmt": "biome format --write .",
|
|
7
|
+
"lint": "biome check .",
|
|
8
|
+
"lint:fix": "biome check --write .",
|
|
9
|
+
"test": "vitest run"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"pi-package"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"picomatch": "^4.0.4"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@biomejs/biome": "^2.5.0",
|
|
19
|
+
"@earendil-works/pi-coding-agent": "^0.79.6",
|
|
20
|
+
"@types/node": "^25.9.3",
|
|
21
|
+
"typebox": "^1.2.16",
|
|
22
|
+
"vitest": "^3"
|
|
23
|
+
},
|
|
24
|
+
"pi": {
|
|
25
|
+
"extensions": [
|
|
26
|
+
"./adapters/pi/index.ts"
|
|
27
|
+
],
|
|
28
|
+
"skills": [
|
|
29
|
+
"./skills"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
32
|
}
|