tdd-enforcer 0.1.1 → 0.1.2

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,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { join, relative } from "node:path";
2
2
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { isToolCallEventType, isBashToolResult } from "@earendil-works/pi-coding-agent";
4
4
  import { isAllowed } from "../../engine/enforce.js";
@@ -44,22 +44,24 @@ export function registerHooks(pi: ExtensionAPI): void {
44
44
  return;
45
45
  }
46
46
 
47
- const allowed = isAllowed(filePath, phase, config);
48
- tddLog(tddDir, "DEBUG", "tool_call: check", { toolName, filePath, phase, allowed });
47
+ // Patterns in rules.json are relative to repo root; convert absolute path
48
+ const relPath = relative(root, filePath);
49
+ const allowed = isAllowed(relPath, phase, config);
50
+ tddLog(tddDir, "DEBUG", "tool_call: check", { toolName, relPath, phase, allowed });
49
51
 
50
52
  if (!allowed) {
51
53
  tddLog(tddDir, "INFO", "tool_call: blocked file modification", {
52
54
  toolName,
53
- filePath,
55
+ relPath,
54
56
  phase,
55
57
  });
56
58
  return {
57
59
  block: true,
58
- reason: `TDD ${phase.toUpperCase()}: "${filePath}" is locked in this phase.`,
60
+ reason: `TDD ${phase.toUpperCase()}: "${relPath}" is locked in this phase.`,
59
61
  };
60
62
  }
61
63
 
62
- tddLog(tddDir, "DEBUG", "tool_call: allowed", { toolName, filePath, phase });
64
+ tddLog(tddDir, "DEBUG", "tool_call: allowed", { toolName, relPath, phase });
63
65
  });
64
66
 
65
67
  pi.on("tool_result", async (event, ctx: ExtensionContext) => {
package/engine/git.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execSync, type ExecSyncOptions } from "node:child_process";
2
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
 
5
5
  const TDD_DIR = ".pi/tdd";
@@ -63,8 +63,29 @@ export function changesSinceSnapshot(projectRoot: string): string[] {
63
63
 
64
64
  export function restoreFiles(projectRoot: string, files: string[]): void {
65
65
  if (files.length === 0) return;
66
- const escaped = files.map((f) => `"${f}"`).join(" ");
67
- gitExec(`restore -- ${escaped}`, projectRoot, { stdio: "pipe" as const });
66
+
67
+ // Separate tracked (git restore) from untracked (delete)
68
+ const tracked = gitExec("ls-files", projectRoot)
69
+ .trim()
70
+ .split("\n")
71
+ .filter(Boolean);
72
+ const trackedSet = new Set(tracked);
73
+
74
+ const trackedFiles = files.filter((f) => trackedSet.has(f));
75
+ const untrackedFiles = files.filter((f) => !trackedSet.has(f));
76
+
77
+ if (trackedFiles.length > 0) {
78
+ const escaped = trackedFiles.map((f) => `"${f}"`).join(" ");
79
+ gitExec(`restore -- ${escaped}`, projectRoot, { stdio: "pipe" as const });
80
+ }
81
+
82
+ for (const f of untrackedFiles) {
83
+ try {
84
+ unlinkSync(f);
85
+ } catch {
86
+ // File may already be gone, ignore
87
+ }
88
+ }
68
89
  }
69
90
 
70
91
  export function headHash(projectRoot: string): string {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tdd-enforcer",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"