tdd-enforcer 0.2.7 → 0.2.8

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.
@@ -1,5 +1,6 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import {
3
+ handleTddJump,
3
4
  handleTddOff,
4
5
  handleTddOn,
5
6
  handleTddReset,
@@ -310,3 +311,108 @@ describe("handleTddReset", () => {
310
311
  expect(ctx.notifications[0].type).toBe("error");
311
312
  });
312
313
  });
314
+
315
+ // ── handleTddJump ───────────────────────────────────────────────────────────
316
+
317
+ describe("handleTddJump", () => {
318
+ let mockLoadTddState: ReturnType<typeof vi.fn>;
319
+ let mockSnapshot: ReturnType<typeof vi.fn>;
320
+ let mockSavePhaseState: ReturnType<typeof vi.fn>;
321
+ let mockTddLog: ReturnType<typeof vi.fn>;
322
+
323
+ function makeDeps(overrides = {}) {
324
+ return {
325
+ loadTddState: mockLoadTddState,
326
+ snapshot: mockSnapshot,
327
+ savePhaseState: mockSavePhaseState,
328
+ tddLog: mockTddLog,
329
+ ...overrides,
330
+ };
331
+ }
332
+
333
+ beforeEach(() => {
334
+ vi.clearAllMocks();
335
+ mockLoadTddState = vi.fn();
336
+ mockSnapshot = vi.fn().mockReturnValue("snap123");
337
+ mockSavePhaseState = vi.fn();
338
+ mockTddLog = vi.fn();
339
+ });
340
+
341
+ function tddOk(overrides?: { current?: string; enabled?: boolean }) {
342
+ return {
343
+ ok: true as const,
344
+ state: {
345
+ enabled: overrides?.enabled ?? true,
346
+ current: overrides?.current ?? "red",
347
+ },
348
+ config,
349
+ };
350
+ }
351
+
352
+ it("shows error when TDD not setup", async () => {
353
+ mockLoadTddState.mockReturnValue({ ok: false, reason: "Missing .pi/tdd/" });
354
+ const ctx = makeCtx();
355
+ await handleTddJump("green", ctx, makeDeps());
356
+ expect(ctx.notifications[0].message).toContain("Missing");
357
+ expect(ctx.notifications[0].type).toBe("error");
358
+ expect(mockSavePhaseState).not.toHaveBeenCalled();
359
+ });
360
+
361
+ it("notifies no-op when already in target phase", async () => {
362
+ mockLoadTddState.mockReturnValue(tddOk({ current: "green" }));
363
+ const ctx = makeCtx();
364
+ await handleTddJump("green", ctx, makeDeps());
365
+ expect(ctx.notifications[0].message).toContain("already in GREEN");
366
+ expect(ctx.notifications[0].type).toBe("info");
367
+ expect(mockSnapshot).not.toHaveBeenCalled();
368
+ expect(mockSavePhaseState).not.toHaveBeenCalled();
369
+ });
370
+
371
+ it("snapshots, auto-enables, jumps phase, notifies", async () => {
372
+ mockLoadTddState.mockReturnValue(tddOk({ current: "red", enabled: true }));
373
+ const ctx = makeCtx();
374
+ await handleTddJump("green", ctx, makeDeps());
375
+
376
+ expect(mockSnapshot).toHaveBeenCalledWith("/test", "red");
377
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
378
+ enabled: true,
379
+ current: "green",
380
+ });
381
+ expect(ctx.notifications[0].message).toContain("Skipped to GREEN phase");
382
+ expect(ctx.notifications[0].type).toBe("info");
383
+ });
384
+
385
+ it("auto-enables when TDD disabled", async () => {
386
+ mockLoadTddState.mockReturnValue(tddOk({ current: "red", enabled: false }));
387
+ const ctx = makeCtx();
388
+ await handleTddJump("green", ctx, makeDeps());
389
+
390
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
391
+ enabled: true,
392
+ current: "green",
393
+ });
394
+ expect(ctx.notifications[0].message).toContain("Skipped to GREEN phase");
395
+ });
396
+
397
+ it("works for refactor from green", async () => {
398
+ mockLoadTddState.mockReturnValue(tddOk({ current: "green" }));
399
+ const ctx = makeCtx();
400
+ await handleTddJump("refactor", ctx, makeDeps());
401
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
402
+ enabled: true,
403
+ current: "refactor",
404
+ });
405
+ expect(ctx.notifications[0].message).toContain("Skipped to REFACTOR phase");
406
+ });
407
+
408
+ it("works for red from green", async () => {
409
+ mockLoadTddState.mockReturnValue(tddOk({ current: "green" }));
410
+ const ctx = makeCtx();
411
+ await handleTddJump("red", ctx, makeDeps());
412
+ expect(mockSavePhaseState).toHaveBeenCalledWith("/test", {
413
+ enabled: true,
414
+ current: "red",
415
+ });
416
+ expect(ctx.notifications[0].message).toContain("Skipped to RED phase");
417
+ });
418
+ });
@@ -148,6 +148,58 @@ export async function handleTddStatus(
148
148
  );
149
149
  }
150
150
 
151
+ export async function handleTddJump(
152
+ phase: "red" | "green" | "refactor",
153
+ ctx: ExtensionContext,
154
+ deps: {
155
+ loadTddState: typeof loadTddState;
156
+ snapshot: typeof snapshot;
157
+ savePhaseState: typeof savePhaseState;
158
+ tddLog: typeof tddLog;
159
+ } = {
160
+ loadTddState,
161
+ snapshot,
162
+ savePhaseState,
163
+ tddLog,
164
+ },
165
+ ): Promise<void> {
166
+ const root = ctx.cwd;
167
+ const tddDir = join(root, ".pi", "tdd");
168
+
169
+ const setup = deps.loadTddState(root);
170
+ if (!setup.ok) {
171
+ deps.tddLog(tddDir, "WARN", `tdd:${phase}: setup invalid`, {
172
+ reason: setup.reason,
173
+ });
174
+ ctx.ui.notify(setup.reason, "error");
175
+ return;
176
+ }
177
+
178
+ const { state } = setup;
179
+
180
+ if (state.current === phase) {
181
+ deps.tddLog(tddDir, "INFO", `tdd:${phase}: already in ${phase}`, {
182
+ phase,
183
+ });
184
+ ctx.ui.notify(`TDD: already in ${phase.toUpperCase()} phase.`, "info");
185
+ return;
186
+ }
187
+
188
+ // Snapshot the current phase's work before jumping
189
+ deps.snapshot(root, state.current);
190
+ deps.tddLog(tddDir, "INFO", `tdd:${phase}: snapshot taken`, {
191
+ from: state.current,
192
+ });
193
+
194
+ // Auto-enable if disabled, set phase
195
+ state.enabled = true;
196
+ state.current = phase;
197
+ deps.savePhaseState(root, state);
198
+
199
+ deps.tddLog(tddDir, "INFO", `tdd:${phase}: jumped`);
200
+ ctx.ui.notify(`Skipped to ${phase.toUpperCase()} phase.`, "info");
201
+ }
202
+
151
203
  export async function handleTddReset(
152
204
  ctx: ExtensionContext,
153
205
  deps: {
@@ -239,6 +291,16 @@ export default function (pi: ExtensionAPI) {
239
291
  handleTddReset(ctx, { ...defaultDeps, resetGit }),
240
292
  });
241
293
 
294
+ for (const phase of ["red", "green", "refactor"] as const) {
295
+ pi.registerCommand(`tdd:${phase}`, {
296
+ description:
297
+ `Skip to ${phase.toUpperCase()} phase. ` +
298
+ "Snapshot working tree, auto-enable TDD, set phase. No gate checks.",
299
+ handler: (_args: string, ctx: ExtensionContext) =>
300
+ handleTddJump(phase, ctx, defaultDeps),
301
+ });
302
+ }
303
+
242
304
  registerTools(pi);
243
305
  registerHooks(pi);
244
306
  }
@@ -11,7 +11,8 @@ export function getNudgePrompt(phase: Phase, config: Config): string {
11
11
  `Blocked files: ${redBlock}\n` +
12
12
  "All other files are free to modify. Call `next_tdd_phase` to proceed to GREEN.\n" +
13
13
  "Think about what could go wrong and test for it — don't just verify the happy path, " +
14
- "cover unhappy paths and edge cases too. Keep cycles small so reverting is cheap."
14
+ "cover unhappy paths and edge cases too.\n" +
15
+ "Keep cycles small so reverting is cheap."
15
16
  );
16
17
  case "green":
17
18
  return (
@@ -72,24 +72,26 @@ describe("executeNextPhase", () => {
72
72
  mockAsyncExec.mockResolvedValue({ stdout: "", stderr: "" });
73
73
  });
74
74
 
75
- it("returns config error when TDD not setup", async () => {
75
+ it("throws when TDD not setup", async () => {
76
76
  mockLoadTddState.mockReturnValue({
77
77
  ok: false,
78
78
  reason:
79
79
  "Missing .pi/tdd/ directory. See the tdd-enforcer skill to learn how to set up TDD configs.",
80
80
  });
81
- const result = await executeNextPhase({ cwd: "/test" } as any, makeDeps());
82
- expect(result.content[0].text).toContain("Missing .pi/tdd/");
81
+ await expect(
82
+ executeNextPhase({ cwd: "/test" } as any, makeDeps()),
83
+ ).rejects.toThrow("Missing .pi/tdd/");
83
84
  });
84
85
 
85
- it("returns disabled message when TDD disabled", async () => {
86
+ it("throws when TDD disabled", async () => {
86
87
  mockLoadTddState.mockReturnValue({
87
88
  ok: true,
88
89
  state: { enabled: false, current: "red" },
89
90
  config: CONFIG,
90
91
  });
91
- const result = await executeNextPhase({ cwd: "/test" } as any, makeDeps());
92
- expect(result.content[0].text).toContain("not enabled");
92
+ await expect(
93
+ executeNextPhase({ cwd: "/test" } as any, makeDeps()),
94
+ ).rejects.toThrow("not enabled");
93
95
  });
94
96
 
95
97
  it("blocks when allowlist violations exist", async () => {
@@ -99,9 +101,9 @@ describe("executeNextPhase", () => {
99
101
  config: CONFIG,
100
102
  });
101
103
  mockGetDisallowedChanges.mockReturnValue(["src/violation.ts"]);
102
- const result = await executeNextPhase({ cwd: "/test" } as any, makeDeps());
103
- expect(result.content[0].text).toContain("BLOCKED");
104
- expect(result.content[0].text).toContain("src/violation.ts");
104
+ await expect(
105
+ executeNextPhase({ cwd: "/test" } as any, makeDeps()),
106
+ ).rejects.toThrow("BLOCKED");
105
107
  });
106
108
 
107
109
  it("blocks red→green when tests pass (need failing test)", async () => {
@@ -115,9 +117,9 @@ describe("executeNextPhase", () => {
115
117
  message:
116
118
  "Tests passed. Add a failing test before transitioning to GREEN.",
117
119
  });
118
- const result = await executeNextPhase({ cwd: "/test" } as any, makeDeps());
119
- expect(result.content[0].text).toContain("Add a failing test");
120
- expect(result.content[0].text).toContain("GREEN");
120
+ await expect(
121
+ executeNextPhase({ cwd: "/test" } as any, makeDeps()),
122
+ ).rejects.toThrow("Add a failing test");
121
123
  });
122
124
 
123
125
  it("blocks green→refactor when tests fail", async () => {
@@ -130,9 +132,9 @@ describe("executeNextPhase", () => {
130
132
  passed: false,
131
133
  message: "Tests failed. Fix them before transitioning to REFACTOR.",
132
134
  });
133
- const result = await executeNextPhase({ cwd: "/test" } as any, makeDeps());
134
- expect(result.content[0].text).toContain("failed");
135
- expect(result.content[0].text).toContain("REFACTOR");
135
+ await expect(
136
+ executeNextPhase({ cwd: "/test" } as any, makeDeps()),
137
+ ).rejects.toThrow("failed");
136
138
  });
137
139
 
138
140
  it("advances red→green when tests fail", async () => {
@@ -185,9 +187,9 @@ describe("executeNextPhase", () => {
185
187
  passed: false,
186
188
  message: "Tests failed. Fix them before transitioning to RED.",
187
189
  });
188
- const result = await executeNextPhase({ cwd: "/test" } as any, makeDeps());
189
- expect(result.content[0].text).toContain("failed");
190
- expect(result.content[0].text).toContain("RED");
190
+ await expect(
191
+ executeNextPhase({ cwd: "/test" } as any, makeDeps()),
192
+ ).rejects.toThrow("failed");
191
193
  expect(mockSavePhaseState).not.toHaveBeenCalled();
192
194
  });
193
195
 
@@ -251,57 +253,49 @@ describe("executePreviousPhase", () => {
251
253
  mockHasParent.mockReturnValue(true);
252
254
  });
253
255
 
254
- it("returns config error when TDD not setup", async () => {
256
+ it("throws when TDD not setup", async () => {
255
257
  mockLoadTddState.mockReturnValue({
256
258
  ok: false,
257
259
  reason: "Missing .pi/tdd/ directory.",
258
260
  });
259
- const result = await executePreviousPhase(
260
- { cwd: "/test" } as any,
261
- makeDeps(),
262
- );
263
- expect(result.content[0].text).toContain("Missing .pi/tdd/");
261
+ await expect(
262
+ executePreviousPhase({ cwd: "/test" } as any, makeDeps()),
263
+ ).rejects.toThrow("Missing .pi/tdd/");
264
264
  });
265
265
 
266
- it("returns disabled message when TDD disabled", async () => {
266
+ it("throws when TDD disabled", async () => {
267
267
  mockLoadTddState.mockReturnValue({
268
268
  ok: true,
269
269
  state: { enabled: false, current: "red" },
270
270
  config: CONFIG,
271
271
  });
272
- const result = await executePreviousPhase(
273
- { cwd: "/test" } as any,
274
- makeDeps(),
275
- );
276
- expect(result.content[0].text).toContain("not enabled");
272
+ await expect(
273
+ executePreviousPhase({ cwd: "/test" } as any, makeDeps()),
274
+ ).rejects.toThrow("not enabled");
277
275
  });
278
276
 
279
- it("returns no-parent message when only init commit exists", async () => {
277
+ it("throws when no parent commit", async () => {
280
278
  mockLoadTddState.mockReturnValue({
281
279
  ok: true,
282
280
  state: { enabled: true, current: "red" },
283
281
  config: CONFIG,
284
282
  });
285
283
  mockHasParent.mockReturnValue(false);
286
- const result = await executePreviousPhase(
287
- { cwd: "/test" } as any,
288
- makeDeps(),
289
- );
290
- expect(result.content[0].text).toContain("No previous phase");
284
+ await expect(
285
+ executePreviousPhase({ cwd: "/test" } as any, makeDeps()),
286
+ ).rejects.toThrow("No previous phase");
291
287
  });
292
288
 
293
- it("returns error when HEAD message is not a TDD snapshot", async () => {
289
+ it("throws when HEAD message is not a TDD snapshot", async () => {
294
290
  mockLoadTddState.mockReturnValue({
295
291
  ok: true,
296
292
  state: { enabled: true, current: "red" },
297
293
  config: CONFIG,
298
294
  });
299
295
  mockHeadMessage.mockReturnValue("garbage");
300
- const result = await executePreviousPhase(
301
- { cwd: "/test" } as any,
302
- makeDeps(),
303
- );
304
- expect(result.content[0].text).toContain("not a TDD snapshot");
296
+ await expect(
297
+ executePreviousPhase({ cwd: "/test" } as any, makeDeps()),
298
+ ).rejects.toThrow("not a TDD snapshot");
305
299
  });
306
300
 
307
301
  it("reverts to previous phase on success", async () => {
@@ -363,23 +357,25 @@ describe("executeTddStatus", () => {
363
357
  mockTddLog = vi.fn();
364
358
  });
365
359
 
366
- it("returns config error when TDD not setup", async () => {
360
+ it("throws when TDD not setup", async () => {
367
361
  mockLoadTddState.mockReturnValue({
368
362
  ok: false,
369
363
  reason: "Missing .pi/tdd/ directory.",
370
364
  });
371
- const result = await executeTddStatus({ cwd: "/test" } as any, makeDeps());
372
- expect(result.content[0].text).toContain("Missing .pi/tdd/");
365
+ await expect(
366
+ executeTddStatus({ cwd: "/test" } as any, makeDeps()),
367
+ ).rejects.toThrow("Missing .pi/tdd/");
373
368
  });
374
369
 
375
- it("returns disabled message when TDD disabled", async () => {
370
+ it("throws when TDD disabled", async () => {
376
371
  mockLoadTddState.mockReturnValue({
377
372
  ok: true,
378
373
  state: { enabled: false, current: "red" },
379
374
  config: CONFIG,
380
375
  });
381
- const result = await executeTddStatus({ cwd: "/test" } as any, makeDeps());
382
- expect(result.content[0].text).toContain("not enabled");
376
+ await expect(
377
+ executeTddStatus({ cwd: "/test" } as any, makeDeps()),
378
+ ).rejects.toThrow("not enabled");
383
379
  });
384
380
 
385
381
  it("returns status details when TDD enabled", async () => {
@@ -102,19 +102,11 @@ export async function executeNextPhase(
102
102
  deps.tddLog(tddDir, "WARN", "next_tdd_phase: TDD not active", {
103
103
  reason: tdd.reason,
104
104
  });
105
- return {
106
- content: [{ type: "text", text: `TDD: ${tdd.reason}` }],
107
- details: {},
108
- };
105
+ throw new Error(`TDD: ${tdd.reason}`);
109
106
  }
110
107
  if (!tdd.state.enabled) {
111
108
  deps.tddLog(tddDir, "WARN", "next_tdd_phase: TDD disabled");
112
- return {
113
- content: [
114
- { type: "text", text: "TDD is not enabled. Run /tdd:on to enable it." },
115
- ],
116
- details: {},
117
- };
109
+ throw new Error("TDD is not enabled. Run /tdd:on to enable it.");
118
110
  }
119
111
 
120
112
  const { state, config } = tdd;
@@ -130,18 +122,11 @@ export async function executeNextPhase(
130
122
  from,
131
123
  violations,
132
124
  });
133
- return {
134
- content: [
135
- {
136
- type: "text",
137
- text:
138
- `BLOCKED: files not allowed in ${from.toUpperCase()} phase:\n` +
139
- violations.map((f) => ` - ${f}`).join("\n") +
140
- `\nRevert or remove them before proceeding.\n\nInspect with: cd .pi/tdd && git diff HEAD -- ${violations[0]}`,
141
- },
142
- ],
143
- details: {},
144
- };
125
+ throw new Error(
126
+ `BLOCKED: files not allowed in ${from.toUpperCase()} phase:\n` +
127
+ violations.map((f) => ` - ${f}`).join("\n") +
128
+ `\nRevert or remove them before proceeding.\n\nInspect with: cd .pi/tdd && git diff HEAD -- ${violations[0]}`,
129
+ );
145
130
  }
146
131
 
147
132
  // 2. Gate check
@@ -176,7 +161,7 @@ export async function executeNextPhase(
176
161
  });
177
162
 
178
163
  if (!gate.passed) {
179
- return { content: [{ type: "text", text: gate.message }], details: {} };
164
+ throw new Error(gate.message);
180
165
  }
181
166
 
182
167
  // 3. Snapshot — label with the phase the work was done in
@@ -214,19 +199,11 @@ export async function executePreviousPhase(
214
199
  deps.tddLog(tddDir, "WARN", "previous_tdd_phase: TDD not active", {
215
200
  reason: tdd.reason,
216
201
  });
217
- return {
218
- content: [{ type: "text", text: `TDD: ${tdd.reason}` }],
219
- details: {},
220
- };
202
+ throw new Error(`TDD: ${tdd.reason}`);
221
203
  }
222
204
  if (!tdd.state.enabled) {
223
205
  deps.tddLog(tddDir, "WARN", "previous_tdd_phase: TDD disabled");
224
- return {
225
- content: [
226
- { type: "text", text: "TDD is not enabled. Run /tdd:on to enable it." },
227
- ],
228
- details: {},
229
- };
206
+ throw new Error("TDD is not enabled. Run /tdd:on to enable it.");
230
207
  }
231
208
 
232
209
  const { state } = tdd;
@@ -235,10 +212,7 @@ export async function executePreviousPhase(
235
212
  deps.tddLog(tddDir, "WARN", "previous_tdd_phase: no parent commit", {
236
213
  phase: state.current,
237
214
  });
238
- return {
239
- content: [{ type: "text", text: "No previous phase to revert to." }],
240
- details: {},
241
- };
215
+ throw new Error("No previous phase to revert to.");
242
216
  }
243
217
 
244
218
  // Read phase from HEAD snapshot commit message (source of truth).
@@ -250,18 +224,11 @@ export async function executePreviousPhase(
250
224
  deps.tddLog(tddDir, "ERROR", "previous_tdd_phase: invalid HEAD message", {
251
225
  headMsg,
252
226
  });
253
- return {
254
- content: [
255
- {
256
- type: "text",
257
- text:
258
- `HEAD commit "${headMsg}" is not a TDD snapshot. Cannot determine previous phase.\n` +
259
- `The private git repo at .pi/tdd must not be manually modified. ` +
260
- `Tampering with it will cause TDD state corruption.`,
261
- },
262
- ],
263
- details: {},
264
- };
227
+ throw new Error(
228
+ `HEAD commit "${headMsg}" is not a TDD snapshot. Cannot determine previous phase.\n` +
229
+ `The private git repo at .pi/tdd must not be manually modified. ` +
230
+ `Tampering with it will cause TDD state corruption.`,
231
+ );
265
232
  }
266
233
  const label = phaseMatch[1];
267
234
  if (label !== "red" && label !== "green" && label !== "refactor") {
@@ -269,15 +236,9 @@ export async function executePreviousPhase(
269
236
  headMsg,
270
237
  label,
271
238
  });
272
- return {
273
- content: [
274
- {
275
- type: "text",
276
- text: `HEAD commit "${headMsg}" has unexpected label. Cannot determine previous phase.`,
277
- },
278
- ],
279
- details: {},
280
- };
239
+ throw new Error(
240
+ `HEAD commit "${headMsg}" has unexpected label. Cannot determine previous phase.`,
241
+ );
281
242
  }
282
243
  const prevPhase: Phase = label;
283
244
  deps.tddLog(tddDir, "INFO", "previous_tdd_phase: reverting", {
@@ -329,19 +290,11 @@ export async function executeTddStatus(
329
290
  deps.tddLog(tddDir, "WARN", "tdd_status: TDD not active", {
330
291
  reason: result.reason,
331
292
  });
332
- return {
333
- content: [{ type: "text", text: `TDD: ${result.reason}` }],
334
- details: {},
335
- };
293
+ throw new Error(`TDD: ${result.reason}`);
336
294
  }
337
295
  if (!result.state.enabled) {
338
296
  deps.tddLog(tddDir, "WARN", "tdd_status: TDD disabled");
339
- return {
340
- content: [
341
- { type: "text", text: "TDD is not enabled. Run /tdd:on to enable it." },
342
- ],
343
- details: {},
344
- };
297
+ throw new Error("TDD is not enabled. Run /tdd:on to enable it.");
345
298
  }
346
299
 
347
300
  const { state, config } = result;
package/package.json CHANGED
@@ -1,32 +1,32 @@
1
1
  {
2
- "name": "tdd-enforcer",
3
- "version": "0.2.7",
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
- }
2
+ "name": "tdd-enforcer",
3
+ "version": "0.2.8",
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
  }