mcp-code-context 2.1.3

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 (112) hide show
  1. package/AGENT_INSTRUCTIONS.md +135 -0
  2. package/CHANGELOG.md +84 -0
  3. package/DEPLOYMENT_INSTRUCTIONS.md +110 -0
  4. package/LICENSE +21 -0
  5. package/README.md +247 -0
  6. package/RELEASE_NOTES_v2.1.3.md +78 -0
  7. package/TROUBLESHOOTING.md +38 -0
  8. package/dist/src/ast/dartCompressor.d.ts +27 -0
  9. package/dist/src/ast/dartCompressor.js +859 -0
  10. package/dist/src/ast/dartCompressor.js.map +1 -0
  11. package/dist/src/ast/phpCompressor.d.ts +24 -0
  12. package/dist/src/ast/phpCompressor.js +634 -0
  13. package/dist/src/ast/phpCompressor.js.map +1 -0
  14. package/dist/src/ast/semanticCompressor.d.ts +22 -0
  15. package/dist/src/ast/semanticCompressor.js +677 -0
  16. package/dist/src/ast/semanticCompressor.js.map +1 -0
  17. package/dist/src/ast/writers/dartWriter.d.ts +37 -0
  18. package/dist/src/ast/writers/dartWriter.js +544 -0
  19. package/dist/src/ast/writers/dartWriter.js.map +1 -0
  20. package/dist/src/ast/writers/phpWriter.d.ts +37 -0
  21. package/dist/src/ast/writers/phpWriter.js +411 -0
  22. package/dist/src/ast/writers/phpWriter.js.map +1 -0
  23. package/dist/src/ast/writers/pyWriter.d.ts +37 -0
  24. package/dist/src/ast/writers/pyWriter.js +355 -0
  25. package/dist/src/ast/writers/pyWriter.js.map +1 -0
  26. package/dist/src/ast/writers/symbolWriter.d.ts +62 -0
  27. package/dist/src/ast/writers/symbolWriter.js +179 -0
  28. package/dist/src/ast/writers/symbolWriter.js.map +1 -0
  29. package/dist/src/ast/writers/tsWriter.d.ts +38 -0
  30. package/dist/src/ast/writers/tsWriter.js +344 -0
  31. package/dist/src/ast/writers/tsWriter.js.map +1 -0
  32. package/dist/src/index.d.ts +117 -0
  33. package/dist/src/index.js +1289 -0
  34. package/dist/src/index.js.map +1 -0
  35. package/dist/src/tests/test-confirmation-flow.d.ts +1 -0
  36. package/dist/src/tests/test-confirmation-flow.js +43 -0
  37. package/dist/src/tests/test-confirmation-flow.js.map +1 -0
  38. package/dist/src/utils/backupManager.d.ts +24 -0
  39. package/dist/src/utils/backupManager.js +59 -0
  40. package/dist/src/utils/backupManager.js.map +1 -0
  41. package/dist/src/utils/confirmationCache.d.ts +16 -0
  42. package/dist/src/utils/confirmationCache.js +40 -0
  43. package/dist/src/utils/confirmationCache.js.map +1 -0
  44. package/dist/src/utils/diffEngine.d.ts +21 -0
  45. package/dist/src/utils/diffEngine.js +163 -0
  46. package/dist/src/utils/diffEngine.js.map +1 -0
  47. package/dist/src/utils/fuzzyMatch.d.ts +8 -0
  48. package/dist/src/utils/fuzzyMatch.js +69 -0
  49. package/dist/src/utils/fuzzyMatch.js.map +1 -0
  50. package/dist/src/utils/ignoreManager.d.ts +22 -0
  51. package/dist/src/utils/ignoreManager.js +135 -0
  52. package/dist/src/utils/ignoreManager.js.map +1 -0
  53. package/dist/tests/debug-replace.d.ts +1 -0
  54. package/dist/tests/debug-replace.js +17 -0
  55. package/dist/tests/debug-replace.js.map +1 -0
  56. package/dist/tests/test-confirmation-flow.d.ts +1 -0
  57. package/dist/tests/test-confirmation-flow.js +43 -0
  58. package/dist/tests/test-confirmation-flow.js.map +1 -0
  59. package/dist/tests/test-dart.d.ts +1 -0
  60. package/dist/tests/test-dart.js +242 -0
  61. package/dist/tests/test-dart.js.map +1 -0
  62. package/dist/tests/test-php-ast.d.ts +1 -0
  63. package/dist/tests/test-php-ast.js +152 -0
  64. package/dist/tests/test-php-ast.js.map +1 -0
  65. package/dist/tests/test-php.d.ts +1 -0
  66. package/dist/tests/test-php.js +234 -0
  67. package/dist/tests/test-php.js.map +1 -0
  68. package/dist/tests/test-smoke.d.ts +5 -0
  69. package/dist/tests/test-smoke.js +83 -0
  70. package/dist/tests/test-smoke.js.map +1 -0
  71. package/dist/tests/writers/test-crlf-handling.d.ts +4 -0
  72. package/dist/tests/writers/test-crlf-handling.js +73 -0
  73. package/dist/tests/writers/test-crlf-handling.js.map +1 -0
  74. package/dist/tests/writers/test-write-smoke.d.ts +9 -0
  75. package/dist/tests/writers/test-write-smoke.js +346 -0
  76. package/dist/tests/writers/test-write-smoke.js.map +1 -0
  77. package/llms.txt +164 -0
  78. package/package.json +57 -0
  79. package/src/ast/dartCompressor.ts +1013 -0
  80. package/src/ast/phpCompressor.ts +814 -0
  81. package/src/ast/semanticCompressor.ts +808 -0
  82. package/src/ast/writers/dartWriter.ts +648 -0
  83. package/src/ast/writers/phpWriter.ts +508 -0
  84. package/src/ast/writers/pyWriter.ts +446 -0
  85. package/src/ast/writers/symbolWriter.ts +277 -0
  86. package/src/ast/writers/tsWriter.ts +418 -0
  87. package/src/index.ts +1553 -0
  88. package/src/utils/backupManager.ts +66 -0
  89. package/src/utils/confirmationCache.ts +61 -0
  90. package/src/utils/diffEngine.ts +223 -0
  91. package/src/utils/fuzzyMatch.ts +86 -0
  92. package/src/utils/ignoreManager.ts +144 -0
  93. package/tests/test-confirmation-flow.ts +49 -0
  94. package/tests/test-dart.d.ts +1 -0
  95. package/tests/test-dart.js +209 -0
  96. package/tests/test-dart.js.map +1 -0
  97. package/tests/test-dart.ts +265 -0
  98. package/tests/test-php-ast.d.ts +1 -0
  99. package/tests/test-php-ast.js +152 -0
  100. package/tests/test-php-ast.js.map +1 -0
  101. package/tests/test-php-ast.ts +155 -0
  102. package/tests/test-php.d.ts +1 -0
  103. package/tests/test-php.js +234 -0
  104. package/tests/test-php.js.map +1 -0
  105. package/tests/test-php.ts +261 -0
  106. package/tests/test-smoke.d.ts +5 -0
  107. package/tests/test-smoke.js +83 -0
  108. package/tests/test-smoke.js.map +1 -0
  109. package/tests/test-smoke.ts +93 -0
  110. package/tests/writers/test-crlf-handling.ts +100 -0
  111. package/tests/writers/test-write-smoke.ts +568 -0
  112. package/tsconfig.json +19 -0
@@ -0,0 +1,568 @@
1
+ /**
2
+ * test-write-smoke.ts — Smoke Tests for Surgical Writing Tools
3
+ *
4
+ * Tests all 4 write operations (replace, insert, rename, remove)
5
+ * across all 4 supported languages (TypeScript, PHP, Dart, Python).
6
+ *
7
+ * Run: npm run build && node dist/tests/writers/test-write-smoke.js
8
+ */
9
+
10
+ import {
11
+ replaceSymbol,
12
+ insertCode,
13
+ renameInFile,
14
+ removeSymbolFromFile,
15
+ } from "../../src/ast/writers/symbolWriter.js";
16
+
17
+ import { generateDiff } from "../../src/utils/diffEngine.js";
18
+
19
+ // ─── Test Harness ───────────────────────────────────────────────────
20
+
21
+ let passed = 0;
22
+ let failed = 0;
23
+
24
+ function assert(condition: boolean, label: string, detail?: string): void {
25
+ if (condition) {
26
+ passed++;
27
+ console.log(` ✅ ${label}`);
28
+ } else {
29
+ failed++;
30
+ console.error(` ❌ ${label}`);
31
+ if (detail) console.error(` ${detail}`);
32
+ }
33
+ }
34
+
35
+ function section(title: string): void {
36
+ console.log(`\n━━━ ${title} ━━━`);
37
+ }
38
+
39
+ // ─── Test Fixtures ──────────────────────────────────────────────────
40
+
41
+ const TS_FIXTURE = `
42
+ import { Something } from "./something.js";
43
+
44
+ const API_URL = "https://example.com";
45
+
46
+ export interface UserConfig {
47
+ name: string;
48
+ email: string;
49
+ }
50
+
51
+ export class UserService {
52
+ private users: Map<string, UserConfig> = new Map();
53
+
54
+ getUser(id: string): UserConfig | undefined {
55
+ return this.users.get(id);
56
+ }
57
+
58
+ addUser(id: string, config: UserConfig): void {
59
+ this.users.set(id, config);
60
+ }
61
+
62
+ deleteUser(id: string): boolean {
63
+ return this.users.delete(id);
64
+ }
65
+ }
66
+
67
+ export function formatUser(user: UserConfig): string {
68
+ return \`\${user.name} <\${user.email}>\`;
69
+ }
70
+ `.trim();
71
+
72
+ const PHP_FIXTURE = `<?php
73
+
74
+ namespace App\\Services;
75
+
76
+ use App\\Models\\User;
77
+ use App\\Contracts\\UserRepositoryInterface;
78
+
79
+ /**
80
+ * Manages user operations.
81
+ */
82
+ class UserService
83
+ {
84
+ private UserRepositoryInterface $repository;
85
+
86
+ public function __construct(UserRepositoryInterface $repository)
87
+ {
88
+ $this->repository = $repository;
89
+ }
90
+
91
+ /**
92
+ * Find a user by ID.
93
+ */
94
+ public function findUser(int $id): ?User
95
+ {
96
+ return $this->repository->find($id);
97
+ }
98
+
99
+ /**
100
+ * Create a new user.
101
+ */
102
+ public function createUser(array $data): User
103
+ {
104
+ return $this->repository->create($data);
105
+ }
106
+
107
+ public function deleteUser(int $id): bool
108
+ {
109
+ return $this->repository->delete($id);
110
+ }
111
+ }
112
+
113
+ function helperFunction(): string
114
+ {
115
+ return 'hello';
116
+ }
117
+ `;
118
+
119
+ const DART_FIXTURE = `
120
+ import 'package:flutter/material.dart';
121
+
122
+ /// A service for managing user data.
123
+ class UserService {
124
+ final Map<String, dynamic> _cache = {};
125
+
126
+ /// Gets a user by ID.
127
+ Future<Map<String, dynamic>?> getUser(String id) async {
128
+ if (_cache.containsKey(id)) {
129
+ return _cache[id];
130
+ }
131
+ return null;
132
+ }
133
+
134
+ /// Adds a user to the cache.
135
+ void addUser(String id, Map<String, dynamic> data) {
136
+ _cache[id] = data;
137
+ }
138
+
139
+ /// Removes a user from the cache.
140
+ bool removeUser(String id) {
141
+ return _cache.remove(id) != null;
142
+ }
143
+ }
144
+
145
+ /// Formats a user name for display.
146
+ String formatUserName(String first, String last) {
147
+ return '\$first \$last';
148
+ }
149
+ `.trim();
150
+
151
+ const PY_FIXTURE = `
152
+ from typing import Optional, Dict
153
+
154
+ class UserService:
155
+ """Manages user operations."""
156
+
157
+ def __init__(self):
158
+ self._users: Dict[str, dict] = {}
159
+
160
+ def get_user(self, user_id: str) -> Optional[dict]:
161
+ """Get a user by ID."""
162
+ return self._users.get(user_id)
163
+
164
+ def add_user(self, user_id: str, data: dict) -> None:
165
+ """Add a user."""
166
+ self._users[user_id] = data
167
+
168
+ def delete_user(self, user_id: str) -> bool:
169
+ """Delete a user."""
170
+ if user_id in self._users:
171
+ del self._users[user_id]
172
+ return True
173
+ return False
174
+
175
+
176
+ def format_user(user: dict) -> str:
177
+ """Format a user for display."""
178
+ return f"{user['name']} <{user['email']}>"
179
+ `.trim();
180
+
181
+ // ─── TypeScript Tests ───────────────────────────────────────────────
182
+
183
+ function testTypeScript(): void {
184
+ section("TypeScript / JavaScript");
185
+
186
+ // Replace
187
+ const replaceResult = replaceSymbol(
188
+ "test.ts",
189
+ TS_FIXTURE,
190
+ "formatUser",
191
+ `export function formatUser(user: UserConfig): string {\n return \`User: \${user.name} (\${user.email})\`;\n}`,
192
+ );
193
+ assert(replaceResult.success, "Replace: formatUser");
194
+ assert(
195
+ replaceResult.newContent.includes("User: ${user.name}"),
196
+ "Replace: new content is present",
197
+ );
198
+ assert(
199
+ replaceResult.newContent.includes("class UserService"),
200
+ "Replace: other symbols preserved",
201
+ );
202
+
203
+ // Insert
204
+ const insertResult = insertCode(
205
+ "test.ts",
206
+ TS_FIXTURE,
207
+ `export function greetUser(user: UserConfig): string {\n return \`Hello, \${user.name}!\`;\n}`,
208
+ "formatUser",
209
+ "after",
210
+ );
211
+ assert(insertResult.success, "Insert: after formatUser");
212
+ assert(
213
+ insertResult.newContent.includes("greetUser"),
214
+ "Insert: new function is present",
215
+ );
216
+
217
+ // Insert inside class
218
+ const insertClassResult = insertCode(
219
+ "test.ts",
220
+ TS_FIXTURE,
221
+ `getUserCount(): number {\n return this.users.size;\n}`,
222
+ "UserService",
223
+ "inside_end",
224
+ );
225
+ assert(insertClassResult.success, "Insert: inside_end of UserService");
226
+ assert(
227
+ insertClassResult.newContent.includes("getUserCount"),
228
+ "Insert: method added to class",
229
+ );
230
+
231
+ // Rename
232
+ const renameResult = renameInFile("test.ts", TS_FIXTURE, "UserService", "AccountService");
233
+ assert(renameResult.success, "Rename: UserService → AccountService");
234
+ assert(
235
+ renameResult.newContent.includes("class AccountService"),
236
+ "Rename: class declaration updated",
237
+ );
238
+ assert(
239
+ !renameResult.newContent.includes("class UserService"),
240
+ "Rename: old name removed",
241
+ );
242
+
243
+ // Remove
244
+ const removeResult = removeSymbolFromFile("test.ts", TS_FIXTURE, "formatUser");
245
+ assert(removeResult.success, "Remove: formatUser");
246
+ assert(
247
+ !removeResult.newContent.includes("function formatUser"),
248
+ "Remove: function is gone",
249
+ );
250
+ assert(
251
+ removeResult.newContent.includes("class UserService"),
252
+ "Remove: other symbols still exist",
253
+ );
254
+
255
+ // Remove interface
256
+ const removeIfaceResult = removeSymbolFromFile("test.ts", TS_FIXTURE, "UserConfig");
257
+ assert(removeIfaceResult.success, "Remove: UserConfig interface");
258
+ assert(
259
+ !removeIfaceResult.newContent.includes("interface UserConfig"),
260
+ "Remove: interface is gone",
261
+ );
262
+ }
263
+
264
+ // ─── PHP Tests ──────────────────────────────────────────────────────
265
+
266
+ function testPHP(): void {
267
+ section("PHP");
268
+
269
+ // Replace
270
+ const replaceResult = replaceSymbol(
271
+ "test.php",
272
+ PHP_FIXTURE,
273
+ "findUser",
274
+ ` public function findUser(int $id): ?User\n {\n $cached = $this->cache->get($id);\n return $cached ?? $this->repository->find($id);\n }`,
275
+ );
276
+ assert(replaceResult.success, "Replace: findUser method");
277
+ assert(
278
+ replaceResult.newContent.includes("$cached"),
279
+ "Replace: new content present",
280
+ );
281
+
282
+ // Insert
283
+ const insertResult = insertCode(
284
+ "test.php",
285
+ PHP_FIXTURE,
286
+ ` public function updateUser(int $id, array $data): User\n {\n return $this->repository->update($id, $data);\n }`,
287
+ "createUser",
288
+ "after",
289
+ );
290
+ assert(insertResult.success, "Insert: after createUser");
291
+ assert(
292
+ insertResult.newContent.includes("updateUser"),
293
+ "Insert: method present",
294
+ );
295
+
296
+ // Rename
297
+ const renameResult = renameInFile("test.php", PHP_FIXTURE, "UserService", "AccountService");
298
+ assert(renameResult.success, "Rename: UserService → AccountService");
299
+ assert(
300
+ renameResult.newContent.includes("class AccountService"),
301
+ "Rename: class updated",
302
+ );
303
+
304
+ // Remove
305
+ const removeResult = removeSymbolFromFile("test.php", PHP_FIXTURE, "helperFunction");
306
+ assert(removeResult.success, "Remove: helperFunction");
307
+ assert(
308
+ !removeResult.newContent.includes("function helperFunction"),
309
+ "Remove: function gone",
310
+ );
311
+ assert(
312
+ removeResult.newContent.includes("class UserService"),
313
+ "Remove: class preserved",
314
+ );
315
+ }
316
+
317
+ // ─── Dart Tests ─────────────────────────────────────────────────────
318
+
319
+ function testDart(): void {
320
+ section("Dart");
321
+
322
+ // Replace
323
+ const replaceResult = replaceSymbol(
324
+ "test.dart",
325
+ DART_FIXTURE,
326
+ "formatUserName",
327
+ `String formatUserName(String first, String last) {\n return '\${last.toUpperCase()}, \$first';\n}`,
328
+ );
329
+ assert(replaceResult.success, "Replace: formatUserName");
330
+ assert(
331
+ replaceResult.newContent.includes("toUpperCase"),
332
+ "Replace: new content present",
333
+ );
334
+
335
+ // Insert
336
+ const insertResult = insertCode(
337
+ "test.dart",
338
+ DART_FIXTURE,
339
+ `/// Checks if a user exists.\nbool userExists(String id) {\n return false;\n}`,
340
+ "formatUserName",
341
+ "after",
342
+ );
343
+ assert(insertResult.success, "Insert: after formatUserName");
344
+ assert(
345
+ insertResult.newContent.includes("userExists"),
346
+ "Insert: function present",
347
+ );
348
+
349
+ // Insert inside class
350
+ const insertClassResult = insertCode(
351
+ "test.dart",
352
+ DART_FIXTURE,
353
+ ` /// Clears the entire cache.\n void clearCache() {\n _cache.clear();\n }`,
354
+ "UserService",
355
+ "inside_end",
356
+ );
357
+ assert(insertClassResult.success, "Insert: inside_end of UserService");
358
+ assert(
359
+ insertClassResult.newContent.includes("clearCache"),
360
+ "Insert: method in class",
361
+ );
362
+
363
+ // Rename
364
+ const renameResult = renameInFile("test.dart", DART_FIXTURE, "UserService", "ProfileService");
365
+ assert(renameResult.success, "Rename: UserService → ProfileService");
366
+ assert(
367
+ renameResult.newContent.includes("class ProfileService"),
368
+ "Rename: class updated",
369
+ );
370
+
371
+ // Remove
372
+ const removeResult = removeSymbolFromFile("test.dart", DART_FIXTURE, "formatUserName");
373
+ assert(removeResult.success, "Remove: formatUserName");
374
+ assert(
375
+ !removeResult.newContent.includes("formatUserName"),
376
+ "Remove: function gone",
377
+ );
378
+ assert(
379
+ removeResult.newContent.includes("class UserService"),
380
+ "Remove: class preserved",
381
+ );
382
+ }
383
+
384
+ // ─── Python Tests ───────────────────────────────────────────────────
385
+
386
+ function testPython(): void {
387
+ section("Python");
388
+
389
+ // Replace
390
+ const replaceResult = replaceSymbol(
391
+ "test.py",
392
+ PY_FIXTURE,
393
+ "format_user",
394
+ `def format_user(user: dict) -> str:\n """Format a user for display with title."""\n name = user['name'].title()\n return f"{name} <{user['email']}>"`,
395
+ );
396
+ assert(replaceResult.success, "Replace: format_user");
397
+ assert(
398
+ replaceResult.newContent.includes(".title()"),
399
+ "Replace: new content present",
400
+ );
401
+
402
+ // Insert
403
+ const insertResult = insertCode(
404
+ "test.py",
405
+ PY_FIXTURE,
406
+ `def validate_email(email: str) -> bool:\n """Check if email is valid."""\n return "@" in email and "." in email`,
407
+ "format_user",
408
+ "after",
409
+ );
410
+ assert(insertResult.success, "Insert: after format_user");
411
+ assert(
412
+ insertResult.newContent.includes("validate_email"),
413
+ "Insert: function present",
414
+ );
415
+
416
+ // Rename
417
+ const renameResult = renameInFile("test.py", PY_FIXTURE, "UserService", "AccountService");
418
+ assert(renameResult.success, "Rename: UserService → AccountService");
419
+ assert(
420
+ renameResult.newContent.includes("class AccountService"),
421
+ "Rename: class updated",
422
+ );
423
+
424
+ // Remove
425
+ const removeResult = removeSymbolFromFile("test.py", PY_FIXTURE, "format_user");
426
+ assert(removeResult.success, "Remove: format_user");
427
+ assert(
428
+ !removeResult.newContent.includes("def format_user"),
429
+ "Remove: function gone",
430
+ );
431
+ assert(
432
+ removeResult.newContent.includes("class UserService"),
433
+ "Remove: class preserved",
434
+ );
435
+ }
436
+
437
+ // ─── Class Name Filtering Tests ───────────────────────────────────────
438
+
439
+ function testClassName(): void {
440
+ section("ClassName Disambiguation");
441
+
442
+ const DART_MULTI_CLASS = `
443
+ class ClassA {
444
+ void build() {
445
+ print('A');
446
+ }
447
+ }
448
+
449
+ class ClassB {
450
+ void build() {
451
+ print('B');
452
+ }
453
+ }
454
+ `;
455
+
456
+ // Try to remove "build" from ClassB
457
+ const removeResult = removeSymbolFromFile(
458
+ "test_multi.dart",
459
+ DART_MULTI_CLASS,
460
+ "build",
461
+ "ClassB",
462
+ );
463
+
464
+ assert(removeResult.success, "ClassName Remove: build from ClassB");
465
+ assert(
466
+ removeResult.newContent.includes("print('A');"),
467
+ "ClassName Remove: ClassA build preserved",
468
+ );
469
+ assert(
470
+ !removeResult.newContent.includes("print('B');"),
471
+ "ClassName Remove: ClassB build removed",
472
+ );
473
+
474
+ const PY_MULTI_CLASS = `
475
+ class ClassA:
476
+ def execute(self):
477
+ return 'A'
478
+
479
+ class ClassB:
480
+ def execute(self):
481
+ return 'B'
482
+ `;
483
+
484
+ const replaceResult = replaceSymbol(
485
+ "test_multi.py",
486
+ PY_MULTI_CLASS,
487
+ "execute",
488
+ " def execute(self):\n return 'C'",
489
+ "ClassB",
490
+ );
491
+
492
+ assert(replaceResult.success, "ClassName Replace: execute in ClassB");
493
+ assert(
494
+ replaceResult.newContent.includes("return 'A'"),
495
+ "ClassName Replace: ClassA execute preserved",
496
+ );
497
+ assert(
498
+ replaceResult.newContent.includes("return 'C'"),
499
+ "ClassName Replace: ClassB execute updated",
500
+ );
501
+ assert(
502
+ !replaceResult.newContent.includes("return 'B'"),
503
+ "ClassName Replace: ClassB execute old content removed",
504
+ );
505
+ }
506
+
507
+ // ─── Diff Engine Tests ──────────────────────────────────────────────
508
+
509
+ function testDiffEngine(): void {
510
+ section("Diff Engine");
511
+
512
+ const oldContent = "line1\nline2\nline3\nline4\nline5";
513
+ const newContent = "line1\nline2_modified\nline3\nline4\nline5\nline6_added";
514
+
515
+ const diff = generateDiff(oldContent, newContent, "test.txt");
516
+
517
+ assert(diff.includes("---"), "Diff: has header");
518
+ assert(diff.includes("+++"), "Diff: has header (new)");
519
+ assert(diff.includes("-line2"), "Diff: shows removal");
520
+ assert(diff.includes("+line2_modified"), "Diff: shows addition");
521
+ assert(diff.includes("+line6_added"), "Diff: shows appended line");
522
+
523
+ // No-change diff
524
+ const noDiff = generateDiff("same", "same", "noop.txt");
525
+ assert(noDiff.includes("no changes"), "Diff: no-change case handled");
526
+ }
527
+
528
+ // ─── Unsupported Language Tests ─────────────────────────────────────
529
+
530
+ function testUnsupported(): void {
531
+ section("Unsupported Languages (Graceful Failures)");
532
+
533
+ const result = replaceSymbol("test.rs", "fn main() {}", "main", "fn main() { println!(\"hi\"); }");
534
+ assert(!result.success, "Unsupported: Rust returns failure");
535
+ assert(
536
+ result.error?.includes("Unsupported") ?? false,
537
+ "Unsupported: error message is descriptive",
538
+ );
539
+ }
540
+
541
+ // ─── Run All ────────────────────────────────────────────────────────
542
+
543
+ async function main(): Promise<void> {
544
+ console.log("🔬 Surgical Writing Tools — Smoke Tests\n");
545
+
546
+ testTypeScript();
547
+ testPHP();
548
+ testDart();
549
+ testPython();
550
+ testClassName();
551
+ testDiffEngine();
552
+ testUnsupported();
553
+
554
+ console.log(`\n${"═".repeat(50)}`);
555
+ console.log(`📊 Results: ${passed} passed, ${failed} failed (${passed + failed} total)`);
556
+
557
+ if (failed > 0) {
558
+ console.error(`\n❌ ${failed} test(s) FAILED`);
559
+ process.exit(1);
560
+ } else {
561
+ console.log(`\n✅ All tests passed!`);
562
+ }
563
+ }
564
+
565
+ main().catch((err) => {
566
+ console.error("Fatal error:", err);
567
+ process.exit(1);
568
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": ".",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "sourceMap": true,
15
+ "isolatedModules": true
16
+ },
17
+ "include": ["src/**/*", "tests/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }