guardrail-core 1.0.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.
Files changed (189) hide show
  1. package/dist/__tests__/autopilot.test.d.ts +7 -0
  2. package/dist/__tests__/autopilot.test.d.ts.map +1 -0
  3. package/dist/__tests__/autopilot.test.js +156 -0
  4. package/dist/__tests__/tier-config.test.d.ts +9 -0
  5. package/dist/__tests__/tier-config.test.d.ts.map +1 -0
  6. package/dist/__tests__/tier-config.test.js +230 -0
  7. package/dist/__tests__/utils/hash-inline.test.d.ts +2 -0
  8. package/dist/__tests__/utils/hash-inline.test.d.ts.map +1 -0
  9. package/dist/__tests__/utils/hash-inline.test.js +62 -0
  10. package/dist/__tests__/utils/hash.test.d.ts +3 -0
  11. package/dist/__tests__/utils/hash.test.d.ts.map +1 -0
  12. package/dist/__tests__/utils/hash.test.js +95 -0
  13. package/dist/__tests__/utils/simple.test.d.ts +1 -0
  14. package/dist/__tests__/utils/simple.test.d.ts.map +1 -0
  15. package/dist/__tests__/utils/simple.test.js +10 -0
  16. package/dist/__tests__/utils/utils-simple.test.d.ts +1 -0
  17. package/dist/__tests__/utils/utils-simple.test.d.ts.map +1 -0
  18. package/dist/__tests__/utils/utils-simple.test.js +6 -0
  19. package/dist/__tests__/utils/utils.test.d.ts +15 -0
  20. package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/utils.test.js +172 -0
  22. package/dist/autopilot/autopilot-runner.d.ts +33 -0
  23. package/dist/autopilot/autopilot-runner.d.ts.map +1 -0
  24. package/dist/autopilot/autopilot-runner.js +479 -0
  25. package/dist/autopilot/index.d.ts +6 -0
  26. package/dist/autopilot/index.d.ts.map +1 -0
  27. package/dist/autopilot/index.js +25 -0
  28. package/dist/autopilot/types.d.ts +102 -0
  29. package/dist/autopilot/types.d.ts.map +1 -0
  30. package/dist/autopilot/types.js +18 -0
  31. package/dist/cache/index.d.ts +7 -0
  32. package/dist/cache/index.d.ts.map +1 -0
  33. package/dist/cache/index.js +22 -0
  34. package/dist/cache/redis-cache.d.ts +145 -0
  35. package/dist/cache/redis-cache.d.ts.map +1 -0
  36. package/dist/cache/redis-cache.js +459 -0
  37. package/dist/ci/github-actions.d.ts +77 -0
  38. package/dist/ci/github-actions.d.ts.map +1 -0
  39. package/dist/ci/github-actions.js +277 -0
  40. package/dist/ci/index.d.ts +12 -0
  41. package/dist/ci/index.d.ts.map +1 -0
  42. package/dist/ci/index.js +27 -0
  43. package/dist/ci/pre-commit.d.ts +65 -0
  44. package/dist/ci/pre-commit.d.ts.map +1 -0
  45. package/dist/ci/pre-commit.js +286 -0
  46. package/dist/entitlements.d.ts +149 -0
  47. package/dist/entitlements.d.ts.map +1 -0
  48. package/dist/entitlements.js +464 -0
  49. package/dist/env.d.ts +113 -0
  50. package/dist/env.d.ts.map +1 -0
  51. package/dist/env.js +204 -0
  52. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts +7 -0
  53. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts.map +1 -0
  54. package/dist/fix-packs/__tests__/generate-fix-packs.test.js +250 -0
  55. package/dist/fix-packs/generate-fix-packs.d.ts +15 -0
  56. package/dist/fix-packs/generate-fix-packs.d.ts.map +1 -0
  57. package/dist/fix-packs/generate-fix-packs.js +505 -0
  58. package/dist/fix-packs/index.d.ts +8 -0
  59. package/dist/fix-packs/index.d.ts.map +1 -0
  60. package/dist/fix-packs/index.js +23 -0
  61. package/dist/fix-packs/types.d.ts +113 -0
  62. package/dist/fix-packs/types.d.ts.map +1 -0
  63. package/dist/fix-packs/types.js +71 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +28 -0
  67. package/dist/metrics/prometheus.d.ts +99 -0
  68. package/dist/metrics/prometheus.d.ts.map +1 -0
  69. package/dist/metrics/prometheus.js +306 -0
  70. package/dist/quota-ledger.d.ts +119 -0
  71. package/dist/quota-ledger.d.ts.map +1 -0
  72. package/dist/quota-ledger.js +462 -0
  73. package/dist/rbac/__tests__/permissions.test.d.ts +8 -0
  74. package/dist/rbac/__tests__/permissions.test.d.ts.map +1 -0
  75. package/dist/rbac/__tests__/permissions.test.js +350 -0
  76. package/dist/rbac/index.d.ts +9 -0
  77. package/dist/rbac/index.d.ts.map +1 -0
  78. package/dist/rbac/index.js +32 -0
  79. package/dist/rbac/permissions.d.ts +71 -0
  80. package/dist/rbac/permissions.d.ts.map +1 -0
  81. package/dist/rbac/permissions.js +247 -0
  82. package/dist/rbac/types.d.ts +69 -0
  83. package/dist/rbac/types.d.ts.map +1 -0
  84. package/dist/rbac/types.js +213 -0
  85. package/dist/tier-config.d.ts +203 -0
  86. package/dist/tier-config.d.ts.map +1 -0
  87. package/dist/tier-config.js +675 -0
  88. package/dist/types.d.ts +365 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +5 -0
  91. package/dist/utils.d.ts +36 -0
  92. package/dist/utils.d.ts.map +1 -0
  93. package/dist/utils.js +127 -0
  94. package/dist/verified-autofix/__tests__/format-validator.test.d.ts +11 -0
  95. package/dist/verified-autofix/__tests__/format-validator.test.d.ts.map +1 -0
  96. package/dist/verified-autofix/__tests__/format-validator.test.js +285 -0
  97. package/dist/verified-autofix/__tests__/pipeline.test.d.ts +11 -0
  98. package/dist/verified-autofix/__tests__/pipeline.test.d.ts.map +1 -0
  99. package/dist/verified-autofix/__tests__/pipeline.test.js +389 -0
  100. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts +11 -0
  101. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts.map +1 -0
  102. package/dist/verified-autofix/__tests__/repo-fingerprint.test.js +236 -0
  103. package/dist/verified-autofix/__tests__/workspace.test.d.ts +11 -0
  104. package/dist/verified-autofix/__tests__/workspace.test.d.ts.map +1 -0
  105. package/dist/verified-autofix/__tests__/workspace.test.js +314 -0
  106. package/dist/verified-autofix/format-validator.d.ts +101 -0
  107. package/dist/verified-autofix/format-validator.d.ts.map +1 -0
  108. package/dist/verified-autofix/format-validator.js +446 -0
  109. package/dist/verified-autofix/index.d.ts +14 -0
  110. package/dist/verified-autofix/index.d.ts.map +1 -0
  111. package/dist/verified-autofix/index.js +39 -0
  112. package/dist/verified-autofix/pipeline.d.ts +68 -0
  113. package/dist/verified-autofix/pipeline.d.ts.map +1 -0
  114. package/dist/verified-autofix/pipeline.js +330 -0
  115. package/dist/verified-autofix/repo-fingerprint.d.ts +56 -0
  116. package/dist/verified-autofix/repo-fingerprint.d.ts.map +1 -0
  117. package/dist/verified-autofix/repo-fingerprint.js +396 -0
  118. package/dist/verified-autofix/workspace.d.ts +83 -0
  119. package/dist/verified-autofix/workspace.d.ts.map +1 -0
  120. package/dist/verified-autofix/workspace.js +454 -0
  121. package/dist/verified-autofix.d.ts +182 -0
  122. package/dist/verified-autofix.d.ts.map +1 -0
  123. package/dist/verified-autofix.js +1021 -0
  124. package/dist/visualization/dependency-graph.d.ts +79 -0
  125. package/dist/visualization/dependency-graph.d.ts.map +1 -0
  126. package/dist/visualization/dependency-graph.js +399 -0
  127. package/dist/visualization/index.d.ts +5 -0
  128. package/dist/visualization/index.d.ts.map +1 -0
  129. package/dist/visualization/index.js +20 -0
  130. package/package.json +29 -0
  131. package/src/__tests__/autopilot.test.ts +196 -0
  132. package/src/__tests__/tier-config.test.ts +289 -0
  133. package/src/__tests__/utils/hash-inline.test.ts +76 -0
  134. package/src/__tests__/utils/hash.test.ts +119 -0
  135. package/src/__tests__/utils/simple.test.ts +10 -0
  136. package/src/__tests__/utils/utils-simple.test.ts +5 -0
  137. package/src/__tests__/utils/utils.test.ts +203 -0
  138. package/src/autopilot/autopilot-runner.ts +503 -0
  139. package/src/autopilot/index.ts +6 -0
  140. package/src/autopilot/types.ts +119 -0
  141. package/src/cache/index.ts +7 -0
  142. package/src/cache/redis-cache.d.ts +155 -0
  143. package/src/cache/redis-cache.d.ts.map +1 -0
  144. package/src/cache/redis-cache.ts +517 -0
  145. package/src/ci/github-actions.ts +335 -0
  146. package/src/ci/index.ts +12 -0
  147. package/src/ci/pre-commit.ts +338 -0
  148. package/src/db/usage-schema.prisma +114 -0
  149. package/src/entitlements.ts +570 -0
  150. package/src/env.d.ts +68 -0
  151. package/src/env.d.ts.map +1 -0
  152. package/src/env.ts +247 -0
  153. package/src/fix-packs/__tests__/generate-fix-packs.test.ts +317 -0
  154. package/src/fix-packs/generate-fix-packs.ts +577 -0
  155. package/src/fix-packs/index.ts +8 -0
  156. package/src/fix-packs/types.ts +206 -0
  157. package/src/index.d.ts +7 -0
  158. package/src/index.d.ts.map +1 -0
  159. package/src/index.ts +12 -0
  160. package/src/metrics/prometheus.d.ts +104 -0
  161. package/src/metrics/prometheus.d.ts.map +1 -0
  162. package/src/metrics/prometheus.ts +446 -0
  163. package/src/quota-ledger.ts +548 -0
  164. package/src/rbac/__tests__/permissions.test.ts +446 -0
  165. package/src/rbac/index.ts +46 -0
  166. package/src/rbac/permissions.ts +301 -0
  167. package/src/rbac/types.ts +298 -0
  168. package/src/tier-config.json +157 -0
  169. package/src/tier-config.ts +815 -0
  170. package/src/types.d.ts +365 -0
  171. package/src/types.d.ts.map +1 -0
  172. package/src/types.ts +441 -0
  173. package/src/utils.d.ts +36 -0
  174. package/src/utils.d.ts.map +1 -0
  175. package/src/utils.ts +140 -0
  176. package/src/verified-autofix/__tests__/format-validator.test.ts +335 -0
  177. package/src/verified-autofix/__tests__/pipeline.test.ts +419 -0
  178. package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +241 -0
  179. package/src/verified-autofix/__tests__/workspace.test.ts +373 -0
  180. package/src/verified-autofix/format-validator.ts +517 -0
  181. package/src/verified-autofix/index.ts +63 -0
  182. package/src/verified-autofix/pipeline.ts +403 -0
  183. package/src/verified-autofix/repo-fingerprint.ts +459 -0
  184. package/src/verified-autofix/workspace.ts +531 -0
  185. package/src/verified-autofix.ts +1187 -0
  186. package/src/visualization/dependency-graph.d.ts +85 -0
  187. package/src/visualization/dependency-graph.d.ts.map +1 -0
  188. package/src/visualization/dependency-graph.ts +495 -0
  189. package/src/visualization/index.ts +5 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Workspace Tests - Temp Workspace Manager
3
+ *
4
+ * Tests for isolated workspace verification:
5
+ * - Workspace creation (copy and worktree)
6
+ * - Diff application via git apply
7
+ * - Verification command execution
8
+ * - Top 3 failure context extraction
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import * as os from 'os';
14
+ import { execSync } from 'child_process';
15
+ import { TempWorkspace } from '../workspace';
16
+ import type { ParsedHunk } from '../format-validator';
17
+ import type { RepoFingerprint } from '../repo-fingerprint';
18
+
19
+ describe('TempWorkspace', () => {
20
+ let tempDir: string;
21
+ let workspace: TempWorkspace;
22
+
23
+ beforeEach(async () => {
24
+ tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'workspace-test-'));
25
+ workspace = new TempWorkspace();
26
+
27
+ // Create minimal project structure
28
+ await fs.promises.writeFile(
29
+ path.join(tempDir, 'package.json'),
30
+ JSON.stringify({
31
+ name: 'test-project',
32
+ version: '1.0.0',
33
+ scripts: {
34
+ build: 'echo "build complete"',
35
+ test: 'echo "tests passed"',
36
+ },
37
+ })
38
+ );
39
+
40
+ // Create a source file
41
+ await fs.promises.mkdir(path.join(tempDir, 'src'), { recursive: true });
42
+ await fs.promises.writeFile(
43
+ path.join(tempDir, 'src', 'index.ts'),
44
+ 'export const version = "1.0.0";\n'
45
+ );
46
+ });
47
+
48
+ afterEach(async () => {
49
+ await workspace.cleanupAll();
50
+ await fs.promises.rm(tempDir, { recursive: true, force: true });
51
+ });
52
+
53
+ describe('create', () => {
54
+ it('creates workspace via copy when not a git repo', async () => {
55
+ const info = await workspace.create({
56
+ projectPath: tempDir,
57
+ useWorktree: true, // Will fall back to copy since no .git
58
+ });
59
+
60
+ expect(info.type).toBe('copy');
61
+ expect(fs.existsSync(info.path)).toBe(true);
62
+ expect(fs.existsSync(path.join(info.path, 'package.json'))).toBe(true);
63
+ expect(fs.existsSync(path.join(info.path, 'src', 'index.ts'))).toBe(true);
64
+ });
65
+
66
+ it('excludes node_modules from copy', async () => {
67
+ // Create fake node_modules
68
+ await fs.promises.mkdir(path.join(tempDir, 'node_modules', 'lodash'), { recursive: true });
69
+ await fs.promises.writeFile(
70
+ path.join(tempDir, 'node_modules', 'lodash', 'index.js'),
71
+ 'module.exports = {};'
72
+ );
73
+
74
+ const info = await workspace.create({
75
+ projectPath: tempDir,
76
+ });
77
+
78
+ expect(fs.existsSync(path.join(info.path, 'node_modules'))).toBe(false);
79
+ });
80
+
81
+ it('excludes .git from copy', async () => {
82
+ // Create fake .git
83
+ await fs.promises.mkdir(path.join(tempDir, '.git'), { recursive: true });
84
+ await fs.promises.writeFile(path.join(tempDir, '.git', 'config'), '');
85
+
86
+ const info = await workspace.create({
87
+ projectPath: tempDir,
88
+ useWorktree: false, // Force copy mode
89
+ });
90
+
91
+ expect(fs.existsSync(path.join(info.path, '.git'))).toBe(false);
92
+ });
93
+ });
94
+
95
+ describe('applyDiff', () => {
96
+ it('applies simple hunk to file', async () => {
97
+ const info = await workspace.create({ projectPath: tempDir });
98
+
99
+ const hunks: ParsedHunk[] = [{
100
+ file: 'src/index.ts',
101
+ oldStart: 1,
102
+ oldLines: 1,
103
+ newStart: 1,
104
+ newLines: 2,
105
+ content: `@@ -1,1 +1,2 @@
106
+ export const version = "1.0.0";
107
+ +export const name = "test";`,
108
+ }];
109
+
110
+ const diff = `--- a/src/index.ts
111
+ +++ b/src/index.ts
112
+ @@ -1,1 +1,2 @@
113
+ export const version = "1.0.0";
114
+ +export const name = "test";`;
115
+
116
+ const result = await workspace.applyDiff(info.path, diff, hunks);
117
+
118
+ expect(result.success).toBe(true);
119
+ expect(result.applied).toBe(1);
120
+ expect(result.errors).toHaveLength(0);
121
+
122
+ // Verify file was modified
123
+ const content = await fs.promises.readFile(
124
+ path.join(info.path, 'src', 'index.ts'),
125
+ 'utf8'
126
+ );
127
+ expect(content).toContain('export const name = "test"');
128
+ });
129
+
130
+ it('creates new file from diff', async () => {
131
+ const info = await workspace.create({ projectPath: tempDir });
132
+
133
+ const hunks: ParsedHunk[] = [{
134
+ file: 'src/utils.ts',
135
+ oldStart: 0,
136
+ oldLines: 0,
137
+ newStart: 1,
138
+ newLines: 1,
139
+ content: `@@ -0,0 +1,1 @@
140
+ +export const add = (a: number, b: number) => a + b;`,
141
+ }];
142
+
143
+ const diff = `--- /dev/null
144
+ +++ b/src/utils.ts
145
+ @@ -0,0 +1,1 @@
146
+ +export const add = (a: number, b: number) => a + b;`;
147
+
148
+ const result = await workspace.applyDiff(info.path, diff, hunks);
149
+
150
+ expect(result.applied).toBeGreaterThan(0);
151
+ expect(fs.existsSync(path.join(info.path, 'src', 'utils.ts'))).toBe(true);
152
+ });
153
+ });
154
+
155
+ describe('verify', () => {
156
+ it('runs verification commands based on fingerprint', async () => {
157
+ const info = await workspace.create({ projectPath: tempDir });
158
+
159
+ const fingerprint: RepoFingerprint = {
160
+ packageManager: 'npm',
161
+ buildTool: 'none',
162
+ framework: 'none',
163
+ testRunner: 'none',
164
+ hasTypeScript: false,
165
+ hasESLint: false,
166
+ hasPrettier: false,
167
+ hasBuildScript: true,
168
+ hasTestScript: true,
169
+ isMonorepo: false,
170
+ workspaces: [],
171
+ dependencies: {},
172
+ devDependencies: {},
173
+ };
174
+
175
+ const result = await workspace.verify(info.path, fingerprint, {
176
+ skipTests: true,
177
+ });
178
+
179
+ // Build should pass (echo command)
180
+ expect(result.passed).toBe(true);
181
+ expect(result.checks.length).toBeGreaterThan(0);
182
+ });
183
+
184
+ it('extracts top 3 failure context on error', async () => {
185
+ const info = await workspace.create({ projectPath: tempDir });
186
+
187
+ // Create a failing build script
188
+ await fs.promises.writeFile(
189
+ path.join(info.path, 'package.json'),
190
+ JSON.stringify({
191
+ name: 'test',
192
+ scripts: {
193
+ build: 'echo "error TS2322: Type mismatch" && echo "error TS2345: Wrong arg" && echo "error TS2339: Missing prop" && exit 1',
194
+ },
195
+ })
196
+ );
197
+
198
+ const fingerprint: RepoFingerprint = {
199
+ packageManager: 'npm',
200
+ buildTool: 'none',
201
+ framework: 'none',
202
+ testRunner: 'none',
203
+ hasTypeScript: false,
204
+ hasESLint: false,
205
+ hasPrettier: false,
206
+ hasBuildScript: true,
207
+ hasTestScript: false,
208
+ isMonorepo: false,
209
+ workspaces: [],
210
+ dependencies: {},
211
+ devDependencies: {},
212
+ };
213
+
214
+ const result = await workspace.verify(info.path, fingerprint, {
215
+ skipTests: true,
216
+ });
217
+
218
+ expect(result.passed).toBe(false);
219
+ expect(result.failureContext.length).toBeLessThanOrEqual(3);
220
+ });
221
+ });
222
+
223
+ describe('copyBack', () => {
224
+ it('copies modified files back to project', async () => {
225
+ const info = await workspace.create({ projectPath: tempDir });
226
+
227
+ // Modify file in workspace
228
+ await fs.promises.writeFile(
229
+ path.join(info.path, 'src', 'index.ts'),
230
+ 'export const version = "2.0.0";\n'
231
+ );
232
+
233
+ await workspace.copyBack(info.path, tempDir, ['src/index.ts']);
234
+
235
+ // Verify original project was updated
236
+ const content = await fs.promises.readFile(
237
+ path.join(tempDir, 'src', 'index.ts'),
238
+ 'utf8'
239
+ );
240
+ expect(content).toBe('export const version = "2.0.0";\n');
241
+ });
242
+
243
+ it('creates backup before overwriting', async () => {
244
+ const info = await workspace.create({ projectPath: tempDir });
245
+
246
+ // Modify file in workspace
247
+ await fs.promises.writeFile(
248
+ path.join(info.path, 'src', 'index.ts'),
249
+ 'export const version = "2.0.0";\n'
250
+ );
251
+
252
+ await workspace.copyBack(info.path, tempDir, ['src/index.ts']);
253
+
254
+ // Backup should exist
255
+ const backupPath = path.join(tempDir, 'src', 'index.ts.guardrail-backup');
256
+ expect(fs.existsSync(backupPath)).toBe(true);
257
+
258
+ const backupContent = await fs.promises.readFile(backupPath, 'utf8');
259
+ expect(backupContent).toBe('export const version = "1.0.0";\n');
260
+ });
261
+ });
262
+
263
+ describe('cleanup', () => {
264
+ it('removes workspace directory', async () => {
265
+ const info = await workspace.create({ projectPath: tempDir });
266
+ const workspacePath = info.path;
267
+
268
+ expect(fs.existsSync(workspacePath)).toBe(true);
269
+
270
+ await workspace.cleanup(info.id);
271
+
272
+ expect(fs.existsSync(workspacePath)).toBe(false);
273
+ });
274
+ });
275
+ });
276
+
277
+ describe('TempWorkspace with git repo', () => {
278
+ let tempDir: string;
279
+ let workspace: TempWorkspace;
280
+ let isGitAvailable: boolean;
281
+
282
+ beforeAll(() => {
283
+ // Check if git is available
284
+ try {
285
+ execSync('git --version', { stdio: 'pipe' });
286
+ isGitAvailable = true;
287
+ } catch {
288
+ isGitAvailable = false;
289
+ }
290
+ });
291
+
292
+ beforeEach(async () => {
293
+ if (!isGitAvailable) return;
294
+
295
+ tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'workspace-git-test-'));
296
+ workspace = new TempWorkspace();
297
+
298
+ // Initialize git repo
299
+ execSync('git init', { cwd: tempDir, stdio: 'pipe' });
300
+ execSync('git config user.email "test@test.com"', { cwd: tempDir, stdio: 'pipe' });
301
+ execSync('git config user.name "Test"', { cwd: tempDir, stdio: 'pipe' });
302
+
303
+ // Create and commit a file
304
+ await fs.promises.writeFile(
305
+ path.join(tempDir, 'file.ts'),
306
+ 'const x = 1;\n'
307
+ );
308
+ execSync('git add .', { cwd: tempDir, stdio: 'pipe' });
309
+ execSync('git commit -m "initial"', { cwd: tempDir, stdio: 'pipe' });
310
+ });
311
+
312
+ afterEach(async () => {
313
+ if (!isGitAvailable) return;
314
+
315
+ await workspace.cleanupAll();
316
+ await fs.promises.rm(tempDir, { recursive: true, force: true });
317
+ });
318
+
319
+ it('uses git worktree when available', async () => {
320
+ if (!isGitAvailable) {
321
+ console.log('Skipping git worktree test - git not available');
322
+ return;
323
+ }
324
+
325
+ const info = await workspace.create({
326
+ projectPath: tempDir,
327
+ useWorktree: true,
328
+ });
329
+
330
+ expect(info.type).toBe('worktree');
331
+ expect(fs.existsSync(path.join(info.path, 'file.ts'))).toBe(true);
332
+ });
333
+
334
+ it('applies diff with git apply', async () => {
335
+ if (!isGitAvailable) {
336
+ console.log('Skipping git apply test - git not available');
337
+ return;
338
+ }
339
+
340
+ const info = await workspace.create({
341
+ projectPath: tempDir,
342
+ useWorktree: true,
343
+ });
344
+
345
+ const diff = `--- a/file.ts
346
+ +++ b/file.ts
347
+ @@ -1 +1,2 @@
348
+ const x = 1;
349
+ +const y = 2;
350
+ `;
351
+
352
+ const hunks: ParsedHunk[] = [{
353
+ file: 'file.ts',
354
+ oldStart: 1,
355
+ oldLines: 1,
356
+ newStart: 1,
357
+ newLines: 2,
358
+ content: `@@ -1 +1,2 @@
359
+ const x = 1;
360
+ +const y = 2;`,
361
+ }];
362
+
363
+ const result = await workspace.applyDiff(info.path, diff, hunks);
364
+
365
+ expect(result.success).toBe(true);
366
+
367
+ const content = await fs.promises.readFile(
368
+ path.join(info.path, 'file.ts'),
369
+ 'utf8'
370
+ );
371
+ expect(content).toContain('const y = 2');
372
+ });
373
+ });