snow-flow 3.4.39 → 3.5.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.
@@ -0,0 +1,705 @@
1
+ "use strict";
2
+ /**
3
+ * Artifact Local Sync System
4
+ *
5
+ * Creates temporary local files from ServiceNow artifacts so Claude Code
6
+ * can use its native tools (search, edit, multi-file operations, etc.)
7
+ * Then syncs changes back to ServiceNow.
8
+ *
9
+ * THIS IS THE BRIDGE BETWEEN SERVICENOW AND CLAUDE CODE!
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.ArtifactLocalSync = void 0;
46
+ const fs = __importStar(require("fs"));
47
+ const path = __importStar(require("path"));
48
+ const os = __importStar(require("os"));
49
+ const smart_field_fetcher_1 = require("./smart-field-fetcher");
50
+ const artifact_registry_1 = require("./artifact-sync/artifact-registry");
51
+ class ArtifactLocalSync {
52
+ constructor(client) {
53
+ this.artifacts = new Map();
54
+ this.client = client;
55
+ this.smartFetcher = new smart_field_fetcher_1.SmartFieldFetcher(client);
56
+ // Create base directory for Snow-Flow artifacts
57
+ this.baseDir = path.join(os.tmpdir(), 'snow-flow-artifacts');
58
+ if (!fs.existsSync(this.baseDir)) {
59
+ fs.mkdirSync(this.baseDir, { recursive: true });
60
+ }
61
+ console.log(`šŸ“ Snow-Flow local sync directory: ${this.baseDir}`);
62
+ }
63
+ /**
64
+ * DYNAMIC pull artifact from ServiceNow using artifact registry
65
+ * Works with ANY artifact type defined in the registry!
66
+ */
67
+ async pullArtifact(tableName, sys_id) {
68
+ const config = (0, artifact_registry_1.getArtifactConfig)(tableName);
69
+ if (!config) {
70
+ throw new Error(`Unsupported artifact type: ${tableName}. Supported types: ${Object.keys(artifact_registry_1.ARTIFACT_REGISTRY).join(', ')}`);
71
+ }
72
+ console.log(`\nšŸ”„ Pulling ${config.displayName} (${sys_id}) to local files...`);
73
+ // Use smart fetcher for known types, otherwise direct query
74
+ let artifactData;
75
+ if (tableName === 'sp_widget') {
76
+ artifactData = await this.smartFetcher.fetchWidget(sys_id);
77
+ }
78
+ else if (tableName === 'sys_hub_flow') {
79
+ artifactData = await this.smartFetcher.fetchFlow(sys_id);
80
+ }
81
+ else if (tableName === 'sys_script') {
82
+ artifactData = await this.smartFetcher.fetchBusinessRule(sys_id);
83
+ }
84
+ else {
85
+ // Generic fetch for other types
86
+ const allFields = config.fieldMappings.map(fm => fm.serviceNowField);
87
+ const response = await this.client.query(tableName, {
88
+ query: `sys_id=${sys_id}`,
89
+ fields: allFields.concat(['sys_id', 'sys_created_on', 'sys_updated_on']),
90
+ limit: 1
91
+ });
92
+ artifactData = response.result?.[0];
93
+ }
94
+ if (!artifactData) {
95
+ throw new Error(`Artifact not found: ${tableName}/${sys_id}`);
96
+ }
97
+ // Use identifier field from config
98
+ const identifier = artifactData[config.identifierField] || `${config.folderName}_${sys_id}`;
99
+ const sanitizedName = this.sanitizeFilename(identifier);
100
+ const artifactPath = path.join(this.baseDir, config.folderName, sanitizedName);
101
+ // Clean up existing files
102
+ if (fs.existsSync(artifactPath)) {
103
+ fs.rmSync(artifactPath, { recursive: true });
104
+ }
105
+ fs.mkdirSync(artifactPath, { recursive: true });
106
+ // Create local files based on field mappings
107
+ const files = [];
108
+ for (const mapping of config.fieldMappings) {
109
+ const fieldValue = artifactData[mapping.serviceNowField];
110
+ // Skip empty fields unless required
111
+ if (!fieldValue && !mapping.isRequired) {
112
+ continue;
113
+ }
114
+ // Apply preprocessor if defined
115
+ let processedContent = fieldValue || '';
116
+ if (mapping.preprocessor) {
117
+ processedContent = mapping.preprocessor(processedContent);
118
+ }
119
+ // Generate filename with placeholder replacement
120
+ const filename = mapping.localFileName
121
+ .replace('{name}', sanitizedName)
122
+ .replace('{api_name}', artifactData.api_name || sanitizedName)
123
+ .replace('{short_description}', artifactData.short_description || sanitizedName);
124
+ // Generate header/footer with replacements
125
+ const header = this.replacePlaceholders(mapping.wrapperHeader || '', artifactData);
126
+ const footer = this.replacePlaceholders(mapping.wrapperFooter || '', artifactData);
127
+ const file = this.createLocalFile(artifactPath, `${filename}.${mapping.fileExtension}`, processedContent, mapping.serviceNowField, mapping.fileExtension, header, footer);
128
+ file.fieldMapping = mapping;
129
+ files.push(file);
130
+ }
131
+ // Generate README with artifact-specific context
132
+ const readmeContent = this.generateArtifactReadme(config, artifactData, files);
133
+ const readmeFile = this.createLocalFile(artifactPath, 'README.md', readmeContent, 'documentation', 'md');
134
+ files.push(readmeFile);
135
+ // Create artifact record
136
+ const artifact = {
137
+ sys_id: artifactData.sys_id,
138
+ name: identifier,
139
+ type: config.displayName,
140
+ tableName: tableName,
141
+ localPath: artifactPath,
142
+ files: files,
143
+ metadata: artifactData,
144
+ syncStatus: 'synced',
145
+ createdAt: new Date(),
146
+ lastSyncedAt: new Date(),
147
+ artifactConfig: config
148
+ };
149
+ this.artifacts.set(sys_id, artifact);
150
+ console.log(`āœ… ${config.displayName} synced to local files:`);
151
+ console.log(`šŸ“ Location: ${artifactPath}`);
152
+ console.log(`šŸ“„ Files created:`);
153
+ files.forEach(f => console.log(` - ${f.filename} (${f.type})`));
154
+ console.log(`\nšŸ’” Claude Code can now use its native tools on these files!`);
155
+ console.log(` Edit, search, refactor - then run 'pushArtifact' to sync back.`);
156
+ return artifact;
157
+ }
158
+ /**
159
+ * Pull a widget from ServiceNow and create local files
160
+ * This is the magic that lets Claude Code use its native tools!
161
+ * (Wrapper for backward compatibility)
162
+ */
163
+ async pullWidget(sys_id) {
164
+ return this.pullArtifact('sp_widget', sys_id);
165
+ console.log(`\nšŸ”„ Pulling widget ${sys_id} to local files...`);
166
+ // Fetch widget with smart chunking
167
+ const widget = await this.smartFetcher.fetchWidget(sys_id);
168
+ // Create local directory structure
169
+ const widgetName = this.sanitizeFilename(widget.name || `widget_${sys_id}`);
170
+ const widgetPath = path.join(this.baseDir, 'widgets', widgetName);
171
+ // Clean up any existing files
172
+ if (fs.existsSync(widgetPath)) {
173
+ fs.rmSync(widgetPath, { recursive: true });
174
+ }
175
+ fs.mkdirSync(widgetPath, { recursive: true });
176
+ // Create local files for each widget component
177
+ const files = [];
178
+ // 1. HTML Template
179
+ if (widget.template) {
180
+ const htmlFile = this.createLocalFile(widgetPath, `${widgetName}.html`, widget.template, 'template', 'html', '<!-- ServiceNow Widget Template -->\n<!-- Widget: ' + widget.name + ' -->\n<!-- Bindings: {{data.x}} from server, ng-click calls client methods -->\n\n');
181
+ files.push(htmlFile);
182
+ }
183
+ // 2. Server Script (ES5)
184
+ if (widget.script) {
185
+ const serverFile = this.createLocalFile(widgetPath, `${widgetName}.server.js`, widget.script, 'script', 'js', '/**\n * ServiceNow Widget Server Script (ES5 ONLY!)\n * Widget: ' + widget.name + '\n * \n * Available objects:\n * - data: Object to send to client\n * - input: Data from client\n * - options: Widget instance options\n * - gs: GlideSystem\n * - $sp: Service Portal API\n */\n\n(function() {\n', '\n})();');
186
+ files.push(serverFile);
187
+ }
188
+ // 3. Client Script (AngularJS)
189
+ if (widget.client_script) {
190
+ const clientFile = this.createLocalFile(widgetPath, `${widgetName}.client.js`, widget.client_script, 'client_script', 'js', '/**\n * ServiceNow Widget Client Controller (AngularJS)\n * Widget: ' + widget.name + '\n * \n * Available objects:\n * - c: Widget controller (this)\n * - c.data: Data from server\n * - c.server: Server communication\n * - $scope: Angular scope\n */\n\nfunction(');
191
+ files.push(clientFile);
192
+ }
193
+ // 4. CSS
194
+ if (widget.css) {
195
+ const cssFile = this.createLocalFile(widgetPath, `${widgetName}.css`, widget.css, 'css', 'css', '/* ServiceNow Widget Styles */\n/* Widget: ' + widget.name + ' */\n/* Prefix classes to avoid conflicts */\n\n');
196
+ files.push(cssFile);
197
+ }
198
+ // 5. Widget Configuration (JSON)
199
+ const config = {
200
+ sys_id: widget.sys_id,
201
+ name: widget.name,
202
+ title: widget.title,
203
+ option_schema: widget.option_schema,
204
+ data_table: widget.data_table,
205
+ roles: widget.roles,
206
+ public: widget.public,
207
+ _coherence_hints: widget._coherence_hints || []
208
+ };
209
+ const configFile = this.createLocalFile(widgetPath, `${widgetName}.config.json`, JSON.stringify(config, null, 2), 'metadata', 'json');
210
+ files.push(configFile);
211
+ // 6. README with context
212
+ const readmeContent = this.generateWidgetReadme(widget, files);
213
+ const readmeFile = this.createLocalFile(widgetPath, 'README.md', readmeContent, 'documentation', 'md');
214
+ files.push(readmeFile);
215
+ // Create artifact record
216
+ const artifact = {
217
+ sys_id: widget.sys_id,
218
+ name: widget.name,
219
+ type: 'widget',
220
+ tableName: 'sp_widget', // Added missing tableName
221
+ localPath: widgetPath,
222
+ files: files,
223
+ metadata: widget,
224
+ syncStatus: 'synced',
225
+ createdAt: new Date(),
226
+ lastSyncedAt: new Date()
227
+ };
228
+ this.artifacts.set(sys_id, artifact);
229
+ console.log(`āœ… Widget synced to local files:`);
230
+ console.log(`šŸ“ Location: ${widgetPath}`);
231
+ console.log(`šŸ“„ Files created:`);
232
+ files.forEach(f => console.log(` - ${f.filename} (${f.type})`));
233
+ console.log(`\nšŸ’” Claude Code can now use its native tools on these files!`);
234
+ console.log(` Edit, search, refactor - then run 'pushWidget' to sync back.`);
235
+ return artifact;
236
+ }
237
+ /**
238
+ * DYNAMIC push local changes back to ServiceNow using artifact registry
239
+ */
240
+ async pushArtifact(sys_id) {
241
+ const artifact = this.artifacts.get(sys_id);
242
+ if (!artifact) {
243
+ throw new Error(`No local artifact found for ${sys_id}. Run pullArtifact first.`);
244
+ }
245
+ const config = artifact.artifactConfig;
246
+ if (!config) {
247
+ throw new Error(`No configuration found for artifact ${sys_id}`);
248
+ }
249
+ console.log(`\nšŸ”„ Pushing local changes back to ServiceNow...`);
250
+ console.log(` Type: ${config.displayName}`);
251
+ console.log(` Table: ${config.tableName}`);
252
+ // Read current content from all files
253
+ const updates = {};
254
+ let hasChanges = false;
255
+ const validationResults = [];
256
+ for (const file of artifact.files) {
257
+ if (fs.existsSync(file.path) && file.fieldMapping) {
258
+ const currentContent = fs.readFileSync(file.path, 'utf8');
259
+ // Strip our added headers/footers for comparison
260
+ const cleanContent = this.stripAddedWrappers(currentContent, file.type, file.fieldMapping);
261
+ if (cleanContent !== file.originalContent) {
262
+ hasChanges = true;
263
+ file.currentContent = cleanContent;
264
+ file.isModified = true;
265
+ // Apply postprocessor if defined
266
+ let processedContent = cleanContent;
267
+ if (file.fieldMapping.postprocessor) {
268
+ processedContent = file.fieldMapping.postprocessor(processedContent);
269
+ }
270
+ // Map back to ServiceNow field
271
+ if (file.field && file.field !== 'documentation' && file.field !== 'metadata') {
272
+ updates[file.field] = processedContent;
273
+ console.log(` šŸ“ Changed: ${file.filename} (${file.field})`);
274
+ // Validate ES5 if required
275
+ if (file.fieldMapping.validateES5) {
276
+ const es5Issues = this.validateES5(processedContent);
277
+ if (es5Issues.length > 0) {
278
+ validationResults.push({
279
+ valid: false,
280
+ errors: es5Issues.map(issue => `${file.field}: ${issue}`),
281
+ warnings: [],
282
+ hints: []
283
+ });
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+ if (!hasChanges) {
291
+ console.log(`āœ… No changes detected. ${config.displayName} is up to date.`);
292
+ return true;
293
+ }
294
+ // Run coherence validation if defined
295
+ if (config.coherenceRules) {
296
+ const fileContents = new Map();
297
+ for (const file of artifact.files) {
298
+ if (file.field && file.currentContent) {
299
+ fileContents.set(file.field, file.currentContent);
300
+ }
301
+ }
302
+ for (const rule of config.coherenceRules) {
303
+ const result = rule.validate(fileContents);
304
+ if (!result.valid) {
305
+ validationResults.push(result);
306
+ }
307
+ }
308
+ }
309
+ // Show validation issues
310
+ if (validationResults.length > 0) {
311
+ console.log(`\nāš ļø Validation Issues Found:`);
312
+ for (const result of validationResults) {
313
+ result.errors.forEach(err => console.log(` āŒ ${err}`));
314
+ result.warnings.forEach(warn => console.log(` āš ļø ${warn}`));
315
+ result.hints.forEach(hint => console.log(` šŸ’” ${hint}`));
316
+ }
317
+ console.log(`\nā“ Continue with deployment anyway? (Issues might cause runtime errors)`);
318
+ // In real implementation, prompt for confirmation
319
+ }
320
+ // Update in ServiceNow
321
+ try {
322
+ console.log(`\nšŸ“¤ Updating ${config.displayName} in ServiceNow...`);
323
+ await this.client.update(config.tableName, sys_id, updates);
324
+ artifact.syncStatus = 'synced';
325
+ artifact.lastSyncedAt = new Date();
326
+ console.log(`āœ… ${config.displayName} successfully updated in ServiceNow!`);
327
+ console.log(`šŸ”— sys_id: ${sys_id}`);
328
+ console.log(`šŸ† All changes have been deployed!`);
329
+ return true;
330
+ }
331
+ catch (error) {
332
+ console.error(`āŒ Failed to update ${config.displayName}:`, error);
333
+ artifact.syncStatus = 'pending_upload';
334
+ return false;
335
+ }
336
+ }
337
+ /**
338
+ * Push local changes back to ServiceNow
339
+ * (Wrapper for backward compatibility)
340
+ */
341
+ async pushWidget(sys_id) {
342
+ return this.pushArtifact(sys_id);
343
+ const artifact = this.artifacts.get(sys_id);
344
+ if (!artifact) {
345
+ throw new Error(`No local artifact found for ${sys_id}. Run pullWidget first.`);
346
+ }
347
+ console.log(`\nšŸ”„ Pushing local changes back to ServiceNow...`);
348
+ // Read current content from all files
349
+ const updates = {};
350
+ let hasChanges = false;
351
+ for (const file of artifact.files) {
352
+ if (fs.existsSync(file.path)) {
353
+ const currentContent = fs.readFileSync(file.path, 'utf8');
354
+ // Strip our added headers/footers for comparison
355
+ const cleanContent = this.stripAddedWrappers(currentContent, file.type);
356
+ if (cleanContent !== file.originalContent) {
357
+ hasChanges = true;
358
+ file.currentContent = cleanContent;
359
+ file.isModified = true;
360
+ // Map back to ServiceNow field
361
+ if (file.field && file.field !== 'documentation' && file.field !== 'metadata') {
362
+ updates[file.field] = cleanContent;
363
+ console.log(` šŸ“ Changed: ${file.filename} (${file.field})`);
364
+ }
365
+ }
366
+ }
367
+ }
368
+ if (!hasChanges) {
369
+ console.log(`āœ… No changes detected. Widget is up to date.`);
370
+ return true;
371
+ }
372
+ // Validate ES5 compliance for server script
373
+ if (updates.script) {
374
+ const es5Issues = this.validateES5(updates.script);
375
+ if (es5Issues.length > 0) {
376
+ console.log(`\nāš ļø ES5 Validation Issues in server script:`);
377
+ es5Issues.forEach(issue => console.log(` - ${issue}`));
378
+ console.log(`\nā“ Continue with deployment anyway? (ServiceNow might fail)`);
379
+ // In real implementation, prompt for confirmation
380
+ }
381
+ }
382
+ // Update in ServiceNow
383
+ try {
384
+ console.log(`\nšŸ“¤ Updating widget in ServiceNow...`);
385
+ await this.client.update('sp_widget', sys_id, updates);
386
+ artifact.syncStatus = 'synced';
387
+ artifact.lastSyncedAt = new Date();
388
+ console.log(`āœ… Widget successfully updated in ServiceNow!`);
389
+ console.log(`šŸ”— sys_id: ${sys_id}`);
390
+ return true;
391
+ }
392
+ catch (error) {
393
+ console.error(`āŒ Failed to update widget:`, error);
394
+ artifact.syncStatus = 'pending_upload';
395
+ return false;
396
+ }
397
+ }
398
+ /**
399
+ * Clean up local files after successful sync
400
+ */
401
+ async cleanup(sys_id, force = false) {
402
+ const artifact = this.artifacts.get(sys_id);
403
+ if (!artifact)
404
+ return;
405
+ if (!force && artifact.syncStatus !== 'synced') {
406
+ console.log(`āš ļø Cannot cleanup - artifact has unsaved changes. Use force=true to override.`);
407
+ return;
408
+ }
409
+ console.log(`\n🧹 Cleaning up local files for ${artifact.name}...`);
410
+ if (fs.existsSync(artifact.localPath)) {
411
+ fs.rmSync(artifact.localPath, { recursive: true });
412
+ }
413
+ this.artifacts.delete(sys_id);
414
+ console.log(`āœ… Local files removed.`);
415
+ }
416
+ /**
417
+ * Create a local file with appropriate headers
418
+ */
419
+ createLocalFile(dirPath, filename, content, field, type, header = '', footer = '') {
420
+ const filePath = path.join(dirPath, filename);
421
+ const fullContent = header + content + footer;
422
+ fs.writeFileSync(filePath, fullContent, 'utf8');
423
+ return {
424
+ filename,
425
+ path: filePath,
426
+ field,
427
+ type: type,
428
+ originalContent: content,
429
+ isModified: false
430
+ };
431
+ }
432
+ /**
433
+ * Generate README with artifact-specific context using registry
434
+ */
435
+ generateArtifactReadme(config, artifact, files) {
436
+ const header = `# ServiceNow ${config.displayName}: ${artifact[config.identifierField] || 'Unnamed'}
437
+
438
+ ## Overview
439
+ - **Type**: ${config.displayName}
440
+ - **Table**: ${config.tableName}
441
+ - **sys_id**: ${artifact.sys_id}
442
+ - **Created**: ${artifact.sys_created_on || 'Unknown'}
443
+ - **Updated**: ${artifact.sys_updated_on || 'Unknown'}
444
+ `;
445
+ const fileSection = `
446
+ ## Files
447
+ ${files.map(f => `- **${f.filename}** - ${f.fieldMapping?.description || f.field}`).join('\n')}
448
+ `;
449
+ // Add coherence rules if defined
450
+ let coherenceSection = '';
451
+ if (config.coherenceRules && config.coherenceRules.length > 0) {
452
+ coherenceSection = `
453
+ ## Validation Rules
454
+ ${config.coherenceRules.map(rule => `### ${rule.name}\n${rule.description}`).join('\n\n')}
455
+ `;
456
+ }
457
+ // Add artifact-specific documentation
458
+ const docSection = config.documentation || '';
459
+ // Add editing instructions
460
+ const instructions = `
461
+ ## Editing Instructions
462
+
463
+ 1. **Edit files** using Claude Code's native tools
464
+ 2. **Maintain coherence** between related files
465
+ ${config.fieldMappings.some(fm => fm.validateES5) ? '3. **Use ES5 only** in server-side scripts (no modern JavaScript)' : ''}
466
+ 4. **Test locally** if possible
467
+ 5. **Run pushArtifact** to sync changes back to ServiceNow
468
+
469
+ ## Commands
470
+
471
+ \`\`\`bash
472
+ # Push changes back to ServiceNow
473
+ snow-flow sync push-artifact ${artifact.sys_id}
474
+
475
+ # Cleanup local files (after sync)
476
+ snow-flow sync cleanup ${artifact.sys_id}
477
+
478
+ # Check sync status
479
+ snow-flow sync status ${artifact.sys_id}
480
+ \`\`\`
481
+ `;
482
+ return header + fileSection + coherenceSection + docSection + instructions;
483
+ }
484
+ /**
485
+ * Replace placeholders in wrapper strings
486
+ */
487
+ replacePlaceholders(template, data) {
488
+ if (!template)
489
+ return '';
490
+ return template
491
+ .replace(/\{name\}/g, data.name || '')
492
+ .replace(/\{api_name\}/g, data.api_name || '')
493
+ .replace(/\{title\}/g, data.title || '')
494
+ .replace(/\{collection\}/g, data.collection || data.table || '')
495
+ .replace(/\{when\}/g, data.when || '')
496
+ .replace(/\{order\}/g, data.order || '')
497
+ .replace(/\{type\}/g, data.type || '')
498
+ .replace(/\{client_callable\}/g, data.client_callable ? 'Client Callable' : 'Server Only')
499
+ .replace(/\{run_as\}/g, data.run_as || '')
500
+ .replace(/\{time_zone\}/g, data.time_zone || '')
501
+ .replace(/\{table\}/g, data.table || '')
502
+ .replace(/\{short_description\}/g, data.short_description || '');
503
+ }
504
+ /**
505
+ * Generate README with widget context
506
+ * (Wrapper for backward compatibility)
507
+ */
508
+ generateWidgetReadme(widget, files) {
509
+ const config = (0, artifact_registry_1.getArtifactConfig)('sp_widget');
510
+ if (!config) {
511
+ throw new Error('Widget configuration not found in registry');
512
+ }
513
+ return this.generateArtifactReadme(config, widget, files);
514
+ return `# ServiceNow Widget: ${widget.name}
515
+
516
+ ## Overview
517
+ - **sys_id**: ${widget.sys_id}
518
+ - **Title**: ${widget.title || 'N/A'}
519
+ - **Created**: ${widget.sys_created_on}
520
+ - **Updated**: ${widget.sys_updated_on}
521
+
522
+ ## Files
523
+ ${files.map(f => `- **${f.filename}** - ${f.field}`).join('\n')}
524
+
525
+ ## Widget Coherence Rules
526
+
527
+ ### Template (HTML)
528
+ - References \`{{data.propertyName}}\` from server script
529
+ - Calls methods via \`ng-click="methodName()"\` from client script
530
+ - Uses CSS classes defined in the CSS file
531
+
532
+ ### Server Script (ES5 ONLY!)
533
+ - Initializes all \`data.*\` properties referenced in template
534
+ - Handles all \`input.action\` requests from client
535
+ - Must use ES5 syntax (no const/let/arrow functions)
536
+
537
+ ### Client Script (AngularJS)
538
+ - Implements all methods called by template ng-click
539
+ - Uses \`c.server.get({action: 'name'})\` to call server
540
+ - Updates \`c.data\` when server responds
541
+
542
+ ### CSS
543
+ - Defines all classes used in template
544
+ - Should be prefixed to avoid conflicts
545
+
546
+ ## Editing Instructions
547
+
548
+ 1. **Edit files** using Claude Code's native tools
549
+ 2. **Maintain coherence** between template, scripts, and CSS
550
+ 3. **Use ES5 only** in server script (no modern JavaScript)
551
+ 4. **Test locally** if possible
552
+ 5. **Run pushWidget** to sync changes back to ServiceNow
553
+
554
+ ## Coherence Hints
555
+ ${widget._coherence_hints?.map((h) => `- ${h}`).join('\n') || 'No automatic hints detected'}
556
+
557
+ ## Commands
558
+
559
+ \`\`\`bash
560
+ # Push changes back to ServiceNow
561
+ snow-flow sync push-widget ${widget.sys_id}
562
+
563
+ # Cleanup local files (after sync)
564
+ snow-flow sync cleanup ${widget.sys_id}
565
+
566
+ # Check sync status
567
+ snow-flow sync status ${widget.sys_id}
568
+ \`\`\`
569
+ `;
570
+ }
571
+ /**
572
+ * Validate ES5 compliance
573
+ */
574
+ validateES5(script) {
575
+ const issues = [];
576
+ // Check for ES6+ syntax
577
+ if (/\bconst\s+/.test(script))
578
+ issues.push('Uses "const" - use "var" instead');
579
+ if (/\blet\s+/.test(script))
580
+ issues.push('Uses "let" - use "var" instead');
581
+ if (/=>\s*{/.test(script))
582
+ issues.push('Uses arrow functions - use function() instead');
583
+ if (/`[^`]*\$\{[^}]*\}[^`]*`/.test(script))
584
+ issues.push('Uses template literals - use string concatenation');
585
+ if (/\.\.\.\w+/.test(script))
586
+ issues.push('Uses spread operator - not supported in ES5');
587
+ if (/class\s+\w+/.test(script))
588
+ issues.push('Uses ES6 classes - use function constructors');
589
+ if (/async\s+function/.test(script))
590
+ issues.push('Uses async/await - use callbacks');
591
+ if (/\bfor\s*\(\s*(?:const|let)\s+\w+\s+of\s+/.test(script))
592
+ issues.push('Uses for...of - use traditional for loop');
593
+ return issues;
594
+ }
595
+ /**
596
+ * Strip headers/footers we added - now uses field mapping for accuracy
597
+ */
598
+ stripAddedWrappers(content, type, fieldMapping) {
599
+ // Remove our added comments and wrappers
600
+ let cleaned = content;
601
+ if (type === 'js') {
602
+ // Remove wrapper function if we added it
603
+ cleaned = cleaned.replace(/^\(function\(\) \{\n/, '');
604
+ cleaned = cleaned.replace(/\n\}\)\(\);$/, '');
605
+ // Remove our header comments
606
+ cleaned = cleaned.replace(/^\/\*\*[\s\S]*?\*\/\n\n/, '');
607
+ cleaned = cleaned.replace(/^function\(/, 'function(');
608
+ }
609
+ else if (type === 'html') {
610
+ // Remove our HTML comments
611
+ cleaned = cleaned.replace(/^<!-- ServiceNow Widget Template -->[\s\S]*?-->\n\n/, '');
612
+ }
613
+ else if (type === 'css') {
614
+ // Remove our CSS comments
615
+ cleaned = cleaned.replace(/^\/\* ServiceNow Widget Styles \*\/[\s\S]*?\*\/\n\n/, '');
616
+ }
617
+ return cleaned.trim();
618
+ }
619
+ /**
620
+ * Sanitize filename for filesystem
621
+ */
622
+ sanitizeFilename(name) {
623
+ return name
624
+ .toLowerCase()
625
+ .replace(/[^a-z0-9_-]/g, '_')
626
+ .replace(/_+/g, '_')
627
+ .substring(0, 50);
628
+ }
629
+ /**
630
+ * List all local artifacts
631
+ */
632
+ listLocalArtifacts() {
633
+ return Array.from(this.artifacts.values());
634
+ }
635
+ /**
636
+ * Get sync status for an artifact
637
+ */
638
+ getSyncStatus(sys_id) {
639
+ const artifact = this.artifacts.get(sys_id);
640
+ return artifact ? artifact.syncStatus : 'not_synced';
641
+ }
642
+ /**
643
+ * Pull any supported artifact type by detecting table from sys_id
644
+ */
645
+ async pullArtifactBySysId(sys_id) {
646
+ // Try to detect table by querying common tables
647
+ const tables = Object.keys(artifact_registry_1.ARTIFACT_REGISTRY);
648
+ for (const table of tables) {
649
+ try {
650
+ const response = await this.client.query(table, {
651
+ query: `sys_id=${sys_id}`,
652
+ fields: ['sys_id'],
653
+ limit: 1
654
+ });
655
+ if (response.result?.[0]) {
656
+ console.log(`šŸŽ† Found artifact in table: ${table}`);
657
+ return this.pullArtifact(table, sys_id);
658
+ }
659
+ }
660
+ catch (error) {
661
+ // Table might not exist, continue searching
662
+ }
663
+ }
664
+ throw new Error(`Could not find artifact with sys_id ${sys_id} in any supported table`);
665
+ }
666
+ /**
667
+ * Get supported artifact types
668
+ */
669
+ getSupportedTypes() {
670
+ return Object.keys(artifact_registry_1.ARTIFACT_REGISTRY);
671
+ }
672
+ /**
673
+ * Validate coherence for an artifact
674
+ */
675
+ async validateArtifactCoherence(sys_id) {
676
+ const artifact = this.artifacts.get(sys_id);
677
+ if (!artifact || !artifact.artifactConfig) {
678
+ throw new Error(`No local artifact found for ${sys_id}`);
679
+ }
680
+ const config = artifact.artifactConfig;
681
+ const results = [];
682
+ if (config.coherenceRules) {
683
+ const fileContents = new Map();
684
+ for (const file of artifact.files) {
685
+ if (file.field && fs.existsSync(file.path)) {
686
+ const content = fs.readFileSync(file.path, 'utf8');
687
+ const cleanContent = this.stripAddedWrappers(content, file.type, file.fieldMapping);
688
+ fileContents.set(file.field, cleanContent);
689
+ }
690
+ }
691
+ for (const rule of config.coherenceRules) {
692
+ const result = rule.validate(fileContents);
693
+ results.push(result);
694
+ }
695
+ }
696
+ // Custom validation if defined
697
+ if (config.customValidation) {
698
+ const customResult = config.customValidation(artifact.metadata);
699
+ results.push(customResult);
700
+ }
701
+ return results;
702
+ }
703
+ }
704
+ exports.ArtifactLocalSync = ArtifactLocalSync;
705
+ //# sourceMappingURL=artifact-local-sync.js.map