tdd-enforcer 0.3.0 → 0.3.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/adapters/pi/index.test.ts +18 -19
- package/adapters/pi/index.ts +301 -296
- package/package.json +30 -30
|
@@ -430,7 +430,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
function
|
|
433
|
+
function _makeEvent(): { systemPrompt: string } {
|
|
434
434
|
return { systemPrompt: "" };
|
|
435
435
|
}
|
|
436
436
|
|
|
@@ -439,7 +439,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
439
439
|
mockLoadTddState = vi.fn();
|
|
440
440
|
});
|
|
441
441
|
|
|
442
|
-
it("
|
|
442
|
+
it("returns instructions when TDD enabled", async () => {
|
|
443
443
|
mockLoadTddState.mockReturnValue({
|
|
444
444
|
ok: true,
|
|
445
445
|
state: { enabled: true, current: "red" },
|
|
@@ -450,33 +450,32 @@ describe("handleBeforeAgentStart", () => {
|
|
|
450
450
|
timeoutSeconds: 30,
|
|
451
451
|
},
|
|
452
452
|
});
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
event as any,
|
|
453
|
+
const result = await handleBeforeAgentStart(
|
|
454
|
+
{ systemPrompt: "" } as any,
|
|
456
455
|
{ cwd: "/test" } as any,
|
|
457
456
|
makeDeps(),
|
|
458
457
|
);
|
|
459
|
-
expect(
|
|
460
|
-
expect(
|
|
461
|
-
expect(
|
|
462
|
-
expect(
|
|
458
|
+
expect(result).toBeDefined();
|
|
459
|
+
expect(result?.systemPrompt).toContain("TDD enforcement");
|
|
460
|
+
expect(result?.systemPrompt).toContain("locked files will be blocked");
|
|
461
|
+
expect(result?.systemPrompt).toContain("cycle so reverting is cheap");
|
|
462
|
+
expect(result?.systemPrompt).not.toContain("next_tdd_phase");
|
|
463
463
|
});
|
|
464
464
|
|
|
465
|
-
it("
|
|
465
|
+
it("returns undefined when TDD not setup", async () => {
|
|
466
466
|
mockLoadTddState.mockReturnValue({
|
|
467
467
|
ok: false,
|
|
468
468
|
reason: "Missing .pi/tdd/",
|
|
469
469
|
});
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
event as any,
|
|
470
|
+
const result = await handleBeforeAgentStart(
|
|
471
|
+
{ systemPrompt: "" } as any,
|
|
473
472
|
{ cwd: "/test" } as any,
|
|
474
473
|
makeDeps(),
|
|
475
474
|
);
|
|
476
|
-
expect(
|
|
475
|
+
expect(result).toBeUndefined();
|
|
477
476
|
});
|
|
478
477
|
|
|
479
|
-
it("
|
|
478
|
+
it("returns disabled message when TDD was disabled", async () => {
|
|
480
479
|
mockLoadTddState.mockReturnValue({
|
|
481
480
|
ok: true,
|
|
482
481
|
state: { enabled: false, current: "red" },
|
|
@@ -487,12 +486,12 @@ describe("handleBeforeAgentStart", () => {
|
|
|
487
486
|
timeoutSeconds: 30,
|
|
488
487
|
},
|
|
489
488
|
});
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
event as any,
|
|
489
|
+
const result = await handleBeforeAgentStart(
|
|
490
|
+
{ systemPrompt: "" } as any,
|
|
493
491
|
{ cwd: "/test" } as any,
|
|
494
492
|
makeDeps(),
|
|
495
493
|
);
|
|
496
|
-
expect(
|
|
494
|
+
expect(result).toBeDefined();
|
|
495
|
+
expect(result?.systemPrompt).toContain("was disabled");
|
|
497
496
|
});
|
|
498
497
|
});
|
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,321 +10,326 @@ 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
|
-
): Promise<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
152
|
+
event: { systemPrompt: string },
|
|
153
|
+
ctx: { cwd: string },
|
|
154
|
+
deps: {
|
|
155
|
+
loadTddState: typeof loadTddState;
|
|
156
|
+
},
|
|
157
|
+
): Promise<{ systemPrompt: string } | undefined> {
|
|
158
|
+
const tdd = deps.loadTddState(ctx.cwd);
|
|
159
|
+
if (!tdd.ok) return;
|
|
160
|
+
|
|
161
|
+
if (tdd.state.enabled) {
|
|
162
|
+
return {
|
|
163
|
+
systemPrompt:
|
|
164
|
+
event.systemPrompt +
|
|
165
|
+
"\n\nYou are working under TDD enforcement. Each phase restricts which files you can modify — locked files will be blocked automatically.\n" +
|
|
166
|
+
"Minimise the scope of each TDD cycle so reverting is cheap.",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
systemPrompt:
|
|
171
|
+
event.systemPrompt +
|
|
172
|
+
"\n\nTDD enforcement was disabled. File restrictions are no longer enforced.",
|
|
173
|
+
};
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
export async function handleTddJump(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
phase: "red" | "green" | "refactor",
|
|
178
|
+
ctx: ExtensionContext,
|
|
179
|
+
deps: {
|
|
180
|
+
loadTddState: typeof loadTddState;
|
|
181
|
+
snapshot: typeof snapshot;
|
|
182
|
+
savePhaseState: typeof savePhaseState;
|
|
183
|
+
tddLog: typeof tddLog;
|
|
184
|
+
} = {
|
|
185
|
+
loadTddState,
|
|
186
|
+
snapshot,
|
|
187
|
+
savePhaseState,
|
|
188
|
+
tddLog,
|
|
189
|
+
},
|
|
185
190
|
): Promise<void> {
|
|
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
|
-
|
|
191
|
+
const root = ctx.cwd;
|
|
192
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
193
|
+
|
|
194
|
+
const setup = deps.loadTddState(root);
|
|
195
|
+
if (!setup.ok) {
|
|
196
|
+
deps.tddLog(tddDir, "WARN", `tdd:${phase}: setup invalid`, {
|
|
197
|
+
reason: setup.reason,
|
|
198
|
+
});
|
|
199
|
+
ctx.ui.notify(setup.reason, "error");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const { state } = setup;
|
|
204
|
+
|
|
205
|
+
if (state.current === phase) {
|
|
206
|
+
deps.tddLog(tddDir, "INFO", `tdd:${phase}: already in ${phase}`, {
|
|
207
|
+
phase,
|
|
208
|
+
});
|
|
209
|
+
ctx.ui.notify(`TDD: already in ${phase.toUpperCase()} phase.`, "info");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Snapshot the current phase's work before jumping
|
|
214
|
+
deps.snapshot(root, state.current);
|
|
215
|
+
deps.tddLog(tddDir, "INFO", `tdd:${phase}: snapshot taken`, {
|
|
216
|
+
from: state.current,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Auto-enable if disabled, set phase
|
|
220
|
+
state.enabled = true;
|
|
221
|
+
state.current = phase;
|
|
222
|
+
deps.savePhaseState(root, state);
|
|
223
|
+
|
|
224
|
+
deps.tddLog(tddDir, "INFO", `tdd:${phase}: jumped`);
|
|
225
|
+
ctx.ui.notify(`Skipped to ${phase.toUpperCase()} phase.`, "info");
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
export async function handleTddReset(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
229
|
+
ctx: ExtensionContext,
|
|
230
|
+
deps: {
|
|
231
|
+
loadTddState: typeof loadTddState;
|
|
232
|
+
resetGit: typeof resetGit;
|
|
233
|
+
snapshot: typeof snapshot;
|
|
234
|
+
savePhaseState: typeof savePhaseState;
|
|
235
|
+
tddLog: typeof tddLog;
|
|
236
|
+
} = {
|
|
237
|
+
loadTddState,
|
|
238
|
+
resetGit,
|
|
239
|
+
snapshot,
|
|
240
|
+
savePhaseState,
|
|
241
|
+
tddLog,
|
|
242
|
+
},
|
|
238
243
|
): Promise<void> {
|
|
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
|
-
|
|
276
|
-
|
|
244
|
+
const root = ctx.cwd;
|
|
245
|
+
const tddDir = join(root, ".pi", "tdd");
|
|
246
|
+
|
|
247
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: starting");
|
|
248
|
+
|
|
249
|
+
const setup = deps.loadTddState(root);
|
|
250
|
+
if (!setup.ok) {
|
|
251
|
+
deps.tddLog(tddDir, "WARN", "tdd:reset: setup invalid", {
|
|
252
|
+
reason: setup.reason,
|
|
253
|
+
});
|
|
254
|
+
ctx.ui.notify(setup.reason, "error");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Nuke git history and re-init
|
|
259
|
+
try {
|
|
260
|
+
deps.resetGit(root);
|
|
261
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: git reset and re-initialised");
|
|
262
|
+
} catch (e) {
|
|
263
|
+
deps.tddLog(tddDir, "ERROR", "tdd:reset: git reset failed", {
|
|
264
|
+
error: (e as Error).message,
|
|
265
|
+
});
|
|
266
|
+
ctx.ui.notify("Failed to reset private git repo.", "error");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Snapshot current working tree
|
|
271
|
+
deps.snapshot(root, "red");
|
|
272
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: snapshot taken");
|
|
273
|
+
|
|
274
|
+
// Reset state to RED (disabled, user must run /tdd:on)
|
|
275
|
+
deps.savePhaseState(root, { enabled: false, current: "red" });
|
|
276
|
+
deps.tddLog(tddDir, "INFO", "tdd:reset: complete");
|
|
277
|
+
|
|
278
|
+
ctx.ui.notify(
|
|
279
|
+
"TDD snapshot history reset. Run /tdd:on to re-enable enforcement.",
|
|
280
|
+
"warning",
|
|
281
|
+
);
|
|
277
282
|
}
|
|
278
283
|
|
|
279
284
|
export default function (pi: ExtensionAPI) {
|
|
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
|
-
|
|
329
|
-
|
|
285
|
+
const defaultDeps = {
|
|
286
|
+
loadTddState,
|
|
287
|
+
snapshot,
|
|
288
|
+
savePhaseState,
|
|
289
|
+
tddLog,
|
|
290
|
+
resetGit,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
pi.registerCommand("tdd:on", {
|
|
294
|
+
description: "Enable TDD enforcement",
|
|
295
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
296
|
+
handleTddOn(ctx, defaultDeps),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
pi.registerCommand("tdd:off", {
|
|
300
|
+
description: "Disable TDD enforcement",
|
|
301
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
302
|
+
handleTddOff(ctx, defaultDeps),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
pi.registerCommand("tdd:status", {
|
|
306
|
+
description: "Show TDD enforcement status",
|
|
307
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
308
|
+
handleTddStatus(ctx, defaultDeps),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
pi.registerCommand("tdd:reset", {
|
|
312
|
+
description:
|
|
313
|
+
"WARNING: Destroys ALL TDD snapshot history and resets to RED phase. " +
|
|
314
|
+
"Working tree is preserved. Run /tdd:on to re-enable after reset.",
|
|
315
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
316
|
+
handleTddReset(ctx, { ...defaultDeps, resetGit }),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
for (const phase of ["red", "green", "refactor"] as const) {
|
|
320
|
+
pi.registerCommand(`tdd:${phase}`, {
|
|
321
|
+
description:
|
|
322
|
+
`Skip to ${phase.toUpperCase()} phase. ` +
|
|
323
|
+
"Snapshot working tree, auto-enable TDD, set phase. No gate checks.",
|
|
324
|
+
handler: (_args: string, ctx: ExtensionContext) =>
|
|
325
|
+
handleTddJump(phase, ctx, defaultDeps),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
registerTools(pi);
|
|
330
|
+
registerHooks(pi);
|
|
331
|
+
|
|
332
|
+
pi.on("before_agent_start", (event, ctx) =>
|
|
333
|
+
handleBeforeAgentStart(event, ctx, { loadTddState }),
|
|
334
|
+
);
|
|
330
335
|
}
|
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.1",
|
|
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
|
}
|