myaidev-method 0.3.4 → 0.3.6

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 (94) hide show
  1. package/.claude-plugin/plugin.json +0 -1
  2. package/.env.example +5 -4
  3. package/CHANGELOG.md +2 -2
  4. package/CONTENT_CREATION_GUIDE.md +489 -3211
  5. package/DEVELOPER_USE_CASES.md +1 -1
  6. package/MODULAR_INSTALLATION.md +2 -2
  7. package/README.md +39 -33
  8. package/TECHNICAL_ARCHITECTURE.md +1 -1
  9. package/USER_GUIDE.md +242 -190
  10. package/agents/content-editor-agent.md +90 -0
  11. package/agents/content-planner-agent.md +97 -0
  12. package/agents/content-research-agent.md +62 -0
  13. package/agents/content-seo-agent.md +101 -0
  14. package/agents/content-writer-agent.md +69 -0
  15. package/agents/infographic-analyzer-agent.md +63 -0
  16. package/agents/infographic-designer-agent.md +72 -0
  17. package/bin/cli.js +846 -427
  18. package/{content-rules.example.md → content-rules-example.md} +2 -2
  19. package/dist/mcp/health-check.js +82 -68
  20. package/dist/mcp/mcp-config.json +8 -0
  21. package/dist/mcp/openstack-server.js +1746 -1262
  22. package/dist/server/.tsbuildinfo +1 -1
  23. package/extension.json +21 -4
  24. package/package.json +181 -184
  25. package/skills/company-config/SKILL.md +133 -0
  26. package/skills/configure/SKILL.md +1 -1
  27. package/skills/myai-configurator/SKILL.md +77 -0
  28. package/skills/myai-configurator/content-creation-configurator/SKILL.md +516 -0
  29. package/skills/myai-configurator/content-maintenance-configurator/SKILL.md +397 -0
  30. package/skills/myai-content-enrichment/SKILL.md +114 -0
  31. package/skills/myai-content-ideation/SKILL.md +288 -0
  32. package/skills/myai-content-ideation/evals/evals.json +182 -0
  33. package/skills/myai-content-production-coordinator/SKILL.md +946 -0
  34. package/skills/{content-rules-setup → myai-content-rules-setup}/SKILL.md +1 -1
  35. package/skills/{content-verifier → myai-content-verifier}/SKILL.md +1 -1
  36. package/skills/myai-content-writer/SKILL.md +333 -0
  37. package/skills/{infographic → myai-infographic}/SKILL.md +1 -1
  38. package/skills/myai-proprietary-content-verifier/SKILL.md +175 -0
  39. package/skills/myai-proprietary-content-verifier/evals/evals.json +36 -0
  40. package/skills/myai-skill-builder/SKILL.md +699 -0
  41. package/skills/myai-skill-builder/agents/analyzer-agent.md +137 -0
  42. package/skills/myai-skill-builder/agents/comparator-agent.md +77 -0
  43. package/skills/myai-skill-builder/agents/grader-agent.md +103 -0
  44. package/skills/myai-skill-builder/assets/eval_review.html +131 -0
  45. package/skills/myai-skill-builder/references/schemas.md +211 -0
  46. package/skills/myai-skill-builder/scripts/aggregate_benchmark.py +190 -0
  47. package/skills/myai-skill-builder/scripts/generate_review.py +381 -0
  48. package/skills/myai-skill-builder/scripts/package_skill.py +91 -0
  49. package/skills/myai-skill-builder/scripts/run_eval.py +105 -0
  50. package/skills/myai-skill-builder/scripts/run_loop.py +211 -0
  51. package/skills/myai-skill-builder/scripts/utils.py +123 -0
  52. package/skills/myai-visual-generator/SKILL.md +125 -0
  53. package/skills/myai-visual-generator/evals/evals.json +155 -0
  54. package/skills/myai-visual-generator/references/infographic-pipeline.md +73 -0
  55. package/skills/myai-visual-generator/references/research-visuals.md +57 -0
  56. package/skills/myai-visual-generator/references/services.md +89 -0
  57. package/skills/myai-visual-generator/scripts/visual-generation-utils.js +1272 -0
  58. package/skills/myaidev-figma/SKILL.md +212 -0
  59. package/skills/myaidev-figma/capture.js +133 -0
  60. package/skills/myaidev-figma/crawl.js +130 -0
  61. package/skills/myaidev-figma-configure/SKILL.md +130 -0
  62. package/skills/openstack-manager/SKILL.md +1 -1
  63. package/skills/payloadcms-publisher/SKILL.md +141 -77
  64. package/skills/payloadcms-publisher/references/field-mapping.md +142 -0
  65. package/skills/payloadcms-publisher/references/lexical-format.md +97 -0
  66. package/skills/security-auditor/SKILL.md +1 -1
  67. package/src/cli/commands/addon.js +105 -7
  68. package/src/config/workflows.js +172 -228
  69. package/src/lib/ascii-banner.js +197 -182
  70. package/src/lib/{content-coordinator.js → content-production-coordinator.js} +649 -459
  71. package/src/lib/installation-detector.js +93 -59
  72. package/src/lib/payloadcms-utils.js +285 -510
  73. package/src/lib/workflow-installer.js +55 -0
  74. package/src/mcp/health-check.js +82 -68
  75. package/src/mcp/openstack-server.js +1746 -1262
  76. package/src/scripts/configure-visual-apis.js +224 -173
  77. package/src/scripts/configure-wordpress-mcp.js +96 -66
  78. package/src/scripts/init/install.js +109 -85
  79. package/src/scripts/init-project.js +138 -67
  80. package/src/scripts/utils/write-content.js +67 -52
  81. package/src/scripts/wordpress/publish-to-wordpress.js +128 -128
  82. package/src/templates/claude/CLAUDE.md +19 -12
  83. package/hooks/hooks.json +0 -26
  84. package/skills/content-coordinator/SKILL.md +0 -130
  85. package/skills/content-enrichment/SKILL.md +0 -80
  86. package/skills/content-writer/SKILL.md +0 -285
  87. package/skills/skill-builder/SKILL.md +0 -417
  88. package/skills/visual-generator/SKILL.md +0 -140
  89. /package/skills/{content-writer → myai-content-writer}/agents/editor-agent.md +0 -0
  90. /package/skills/{content-writer → myai-content-writer}/agents/planner-agent.md +0 -0
  91. /package/skills/{content-writer → myai-content-writer}/agents/research-agent.md +0 -0
  92. /package/skills/{content-writer → myai-content-writer}/agents/seo-agent.md +0 -0
  93. /package/skills/{content-writer → myai-content-writer}/agents/visual-planner-agent.md +0 -0
  94. /package/skills/{content-writer → myai-content-writer}/agents/writer-agent.md +0 -0
@@ -5,64 +5,64 @@
5
5
  * Provides checkpoint/resume capability, error isolation, progress tracking,
6
6
  * multi-platform publishing, analytics, webhooks, and queue management.
7
7
  *
8
- * @module content-coordinator
8
+ * @module content-production-coordinator
9
9
  */
10
10
 
11
- import fs from 'fs/promises';
12
- import path from 'path';
13
- import https from 'https';
14
- import http from 'http';
15
- import os from 'os';
11
+ import fs from "fs/promises";
12
+ import path from "path";
13
+ import https from "https";
14
+ import http from "http";
15
+ import os from "os";
16
16
 
17
17
  /**
18
18
  * Workflow phases for the content production coordinator
19
19
  */
20
20
  const PHASES = {
21
- INITIALIZE: 'initialize',
22
- VERIFY: 'verify',
23
- CATEGORIZE: 'categorize',
24
- REPORT: 'report',
25
- NOTIFY: 'notify',
26
- PUBLISH: 'publish',
27
- COMPLETE: 'complete'
21
+ INITIALIZE: "initialize",
22
+ VERIFY: "verify",
23
+ CATEGORIZE: "categorize",
24
+ REPORT: "report",
25
+ NOTIFY: "notify",
26
+ PUBLISH: "publish",
27
+ COMPLETE: "complete",
28
28
  };
29
29
 
30
30
  /**
31
31
  * Content item status values
32
32
  */
33
33
  const ITEM_STATUS = {
34
- PENDING: 'pending',
35
- VERIFYING: 'verifying',
36
- VERIFIED: 'verified',
37
- READY: 'ready',
38
- NEEDS_REVIEW: 'needs_review',
39
- PUBLISHING: 'publishing',
40
- PUBLISHED: 'published',
41
- SCHEDULED: 'scheduled',
42
- FAILED: 'failed',
43
- SKIPPED: 'skipped'
34
+ PENDING: "pending",
35
+ VERIFYING: "verifying",
36
+ VERIFIED: "verified",
37
+ READY: "ready",
38
+ NEEDS_REVIEW: "needs_review",
39
+ PUBLISHING: "publishing",
40
+ PUBLISHED: "published",
41
+ SCHEDULED: "scheduled",
42
+ FAILED: "failed",
43
+ SKIPPED: "skipped",
44
44
  };
45
45
 
46
46
  /**
47
47
  * Redundancy score thresholds
48
48
  */
49
49
  const REDUNDANCY_THRESHOLDS = {
50
- MINIMAL: 'minimal',
51
- LOW: 'low',
52
- MEDIUM: 'medium',
53
- HIGH: 'high'
50
+ MINIMAL: "minimal",
51
+ LOW: "low",
52
+ MEDIUM: "medium",
53
+ HIGH: "high",
54
54
  };
55
55
 
56
56
  /**
57
57
  * Supported publishing platforms
58
58
  */
59
59
  const PLATFORMS = {
60
- WORDPRESS: 'wordpress',
61
- PAYLOADCMS: 'payloadcms',
62
- STATIC: 'static',
63
- DOCUSAURUS: 'docusaurus',
64
- MINTLIFY: 'mintlify',
65
- ASTRO: 'astro'
60
+ WORDPRESS: "wordpress",
61
+ PAYLOADCMS: "payloadcms",
62
+ STATIC: "static",
63
+ DOCUSAURUS: "docusaurus",
64
+ MINTLIFY: "mintlify",
65
+ ASTRO: "astro",
66
66
  };
67
67
 
68
68
  /**
@@ -73,9 +73,9 @@ const DEFAULT_CONFIG = {
73
73
  retryAttempts: 2,
74
74
  retryDelay: 1000,
75
75
  checkpointInterval: 5000,
76
- stateFileName: '.content-coordinator-state.json',
77
- queueFileName: '.content-queue.json',
78
- outputDir: '.',
76
+ stateFileName: ".content-production-coordinator-state.json",
77
+ queueFileName: ".content-queue.json",
78
+ outputDir: ".",
79
79
  dryRun: false,
80
80
  force: false,
81
81
  verbose: false,
@@ -83,21 +83,21 @@ const DEFAULT_CONFIG = {
83
83
  contentRulesPath: null,
84
84
  // Webhooks
85
85
  webhookUrl: null,
86
- webhookEvents: ['complete', 'error'],
86
+ webhookEvents: ["complete", "error"],
87
87
  // Analytics
88
88
  enableAnalytics: true,
89
89
  // Platform
90
90
  defaultPlatform: PLATFORMS.WORDPRESS,
91
91
  // Cron/Scheduling
92
92
  cronMode: false,
93
- lockFileName: '.content-coordinator.lock',
94
- lastRunFileName: '.content-coordinator-lastrun.json',
93
+ lockFileName: ".content-production-coordinator.lock",
94
+ lastRunFileName: ".content-production-coordinator-lastrun.json",
95
95
  minRunInterval: 0, // Minimum seconds between runs (0 = no limit)
96
96
  quietMode: false, // Suppress non-error output for cron
97
97
  // WordPress Scheduling
98
98
  useWordPressScheduling: true, // Use WP native scheduling for future posts
99
99
  defaultPublishDelay: 0, // Hours to delay publication (0 = immediate)
100
- publishSpreadInterval: 0 // Hours between scheduled posts (0 = no spreading)
100
+ publishSpreadInterval: 0, // Hours between scheduled posts (0 = no spreading)
101
101
  };
102
102
 
103
103
  /**
@@ -118,13 +118,13 @@ class ContentRulesManager {
118
118
  if (!this.rulesPath) return null;
119
119
 
120
120
  try {
121
- const content = await fs.readFile(this.rulesPath, 'utf8');
121
+ const content = await fs.readFile(this.rulesPath, "utf8");
122
122
  this.rules = this.parseContentRules(content);
123
123
  this.loaded = true;
124
124
  return this.rules;
125
125
  } catch (err) {
126
- if (err.code !== 'ENOENT') {
127
- console.error('[ContentRules] Error loading rules:', err.message);
126
+ if (err.code !== "ENOENT") {
127
+ console.error("[ContentRules] Error loading rules:", err.message);
128
128
  }
129
129
  return null;
130
130
  }
@@ -142,28 +142,28 @@ class ContentRulesManager {
142
142
  writingStyle: {},
143
143
  seoGuidelines: {},
144
144
  formatting: {},
145
- contentBoundaries: {}
145
+ contentBoundaries: {},
146
146
  };
147
147
 
148
148
  // Extract sections using markdown headers
149
149
  const sections = content.split(/^## /m).slice(1);
150
150
 
151
151
  for (const section of sections) {
152
- const lines = section.split('\n');
152
+ const lines = section.split("\n");
153
153
  const header = lines[0].trim().toLowerCase();
154
- const body = lines.slice(1).join('\n').trim();
154
+ const body = lines.slice(1).join("\n").trim();
155
155
 
156
- if (header.includes('brand') || header.includes('identity')) {
156
+ if (header.includes("brand") || header.includes("identity")) {
157
157
  rules.brandIdentity = this.parseSection(body);
158
- } else if (header.includes('voice') || header.includes('tone')) {
158
+ } else if (header.includes("voice") || header.includes("tone")) {
159
159
  rules.voiceTone = this.parseSection(body);
160
- } else if (header.includes('writing') || header.includes('style')) {
160
+ } else if (header.includes("writing") || header.includes("style")) {
161
161
  rules.writingStyle = this.parseSection(body);
162
- } else if (header.includes('seo')) {
162
+ } else if (header.includes("seo")) {
163
163
  rules.seoGuidelines = this.parseSection(body);
164
- } else if (header.includes('format')) {
164
+ } else if (header.includes("format")) {
165
165
  rules.formatting = this.parseSection(body);
166
- } else if (header.includes('boundar') || header.includes('avoid')) {
166
+ } else if (header.includes("boundar") || header.includes("avoid")) {
167
167
  rules.contentBoundaries = this.parseSection(body);
168
168
  }
169
169
  }
@@ -178,13 +178,13 @@ class ContentRulesManager {
178
178
  */
179
179
  parseSection(body) {
180
180
  const result = {};
181
- const lines = body.split('\n');
181
+ const lines = body.split("\n");
182
182
 
183
183
  for (const line of lines) {
184
184
  // Parse list items: - **Key**: Value
185
185
  const match = line.match(/^[-*]\s*\*\*([^*]+)\*\*:\s*(.+)$/);
186
186
  if (match) {
187
- const key = match[1].trim().toLowerCase().replace(/\s+/g, '_');
187
+ const key = match[1].trim().toLowerCase().replace(/\s+/g, "_");
188
188
  result[key] = match[2].trim();
189
189
  }
190
190
  // Parse simple list items: - Value
@@ -209,7 +209,7 @@ class ContentRulesManager {
209
209
  brandVoice: this.rules.voiceTone,
210
210
  writingStyle: this.rules.writingStyle,
211
211
  contentBoundaries: this.rules.contentBoundaries,
212
- checkBrandAlignment: true
212
+ checkBrandAlignment: true,
213
213
  };
214
214
  }
215
215
 
@@ -223,7 +223,7 @@ class ContentRulesManager {
223
223
  return {
224
224
  brandIdentity: this.rules.brandIdentity,
225
225
  seoGuidelines: this.rules.seoGuidelines,
226
- formatting: this.rules.formatting
226
+ formatting: this.rules.formatting,
227
227
  };
228
228
  }
229
229
  }
@@ -238,27 +238,27 @@ class AnalyticsTracker {
238
238
  startTime: null,
239
239
  endTime: null,
240
240
  duration: null,
241
- phases: {}
241
+ phases: {},
242
242
  },
243
243
  items: {
244
244
  total: 0,
245
245
  verified: 0,
246
246
  published: 0,
247
247
  failed: 0,
248
- skipped: 0
248
+ skipped: 0,
249
249
  },
250
250
  performance: {
251
251
  avgVerificationTime: 0,
252
252
  avgPublishTime: 0,
253
253
  totalVerificationTime: 0,
254
- totalPublishTime: 0
254
+ totalPublishTime: 0,
255
255
  },
256
256
  quality: {
257
257
  avgRedundancyScore: 0,
258
258
  avgUniquenessScore: 0,
259
- contentQualityTrend: []
259
+ contentQualityTrend: [],
260
260
  },
261
- errors: []
261
+ errors: [],
262
262
  };
263
263
  this.phaseTimers = {};
264
264
  }
@@ -275,7 +275,8 @@ class AnalyticsTracker {
275
275
  */
276
276
  endWorkflow() {
277
277
  this.metrics.workflow.endTime = Date.now();
278
- this.metrics.workflow.duration = this.metrics.workflow.endTime - this.metrics.workflow.startTime;
278
+ this.metrics.workflow.duration =
279
+ this.metrics.workflow.endTime - this.metrics.workflow.startTime;
279
280
  }
280
281
 
281
282
  /**
@@ -294,7 +295,7 @@ class AnalyticsTracker {
294
295
  if (this.phaseTimers[phase]) {
295
296
  this.metrics.workflow.phases[phase] = {
296
297
  duration: Date.now() - this.phaseTimers[phase],
297
- completedAt: new Date().toISOString()
298
+ completedAt: new Date().toISOString(),
298
299
  };
299
300
  }
300
301
  }
@@ -308,18 +309,19 @@ class AnalyticsTracker {
308
309
  this.metrics.items.verified++;
309
310
  this.metrics.performance.totalVerificationTime += duration;
310
311
  this.metrics.performance.avgVerificationTime =
311
- this.metrics.performance.totalVerificationTime / this.metrics.items.verified;
312
+ this.metrics.performance.totalVerificationTime /
313
+ this.metrics.items.verified;
312
314
 
313
315
  // Track quality metrics
314
316
  if (item.verification) {
315
317
  const scores = { minimal: 1, low: 2, medium: 3, high: 4 };
316
- const score = (item.verification.redundancyScore || '').toLowerCase();
318
+ const score = (item.verification.redundancyScore || "").toLowerCase();
317
319
  if (scores[score]) {
318
320
  this.metrics.quality.contentQualityTrend.push({
319
321
  item: item.metadata.title,
320
322
  redundancyScore: scores[score],
321
323
  uniquenessScore: item.verification.uniquenessScore || 0,
322
- timestamp: new Date().toISOString()
324
+ timestamp: new Date().toISOString(),
323
325
  });
324
326
 
325
327
  // Update averages
@@ -327,7 +329,8 @@ class AnalyticsTracker {
327
329
  this.metrics.quality.avgRedundancyScore =
328
330
  trend.reduce((sum, t) => sum + t.redundancyScore, 0) / trend.length;
329
331
  this.metrics.quality.avgUniquenessScore =
330
- trend.reduce((sum, t) => sum + (t.uniquenessScore || 0), 0) / trend.length;
332
+ trend.reduce((sum, t) => sum + (t.uniquenessScore || 0), 0) /
333
+ trend.length;
331
334
  }
332
335
  }
333
336
  }
@@ -353,10 +356,10 @@ class AnalyticsTracker {
353
356
  trackError(item, error, phase) {
354
357
  this.metrics.items.failed++;
355
358
  this.metrics.errors.push({
356
- item: item.metadata?.title || 'Unknown',
359
+ item: item.metadata?.title || "Unknown",
357
360
  error: error.message,
358
361
  phase,
359
- timestamp: new Date().toISOString()
362
+ timestamp: new Date().toISOString(),
360
363
  });
361
364
  }
362
365
 
@@ -373,19 +376,28 @@ class AnalyticsTracker {
373
376
  * @returns {Object} Analytics summary
374
377
  */
375
378
  getSummary() {
376
- const successRate = this.metrics.items.total > 0
377
- ? ((this.metrics.items.published / this.metrics.items.total) * 100).toFixed(1)
378
- : 0;
379
+ const successRate =
380
+ this.metrics.items.total > 0
381
+ ? (
382
+ (this.metrics.items.published / this.metrics.items.total) *
383
+ 100
384
+ ).toFixed(1)
385
+ : 0;
379
386
 
380
387
  return {
381
388
  ...this.metrics,
382
389
  summary: {
383
390
  successRate: `${successRate}%`,
384
391
  totalDuration: this.formatDuration(this.metrics.workflow.duration),
385
- avgVerificationTime: this.formatDuration(this.metrics.performance.avgVerificationTime),
386
- avgPublishTime: this.formatDuration(this.metrics.performance.avgPublishTime),
387
- avgQualityScore: (5 - this.metrics.quality.avgRedundancyScore).toFixed(2) + '/4'
388
- }
392
+ avgVerificationTime: this.formatDuration(
393
+ this.metrics.performance.avgVerificationTime,
394
+ ),
395
+ avgPublishTime: this.formatDuration(
396
+ this.metrics.performance.avgPublishTime,
397
+ ),
398
+ avgQualityScore:
399
+ (5 - this.metrics.quality.avgRedundancyScore).toFixed(2) + "/4",
400
+ },
389
401
  };
390
402
  }
391
403
 
@@ -395,7 +407,7 @@ class AnalyticsTracker {
395
407
  * @returns {string} Formatted duration
396
408
  */
397
409
  formatDuration(ms) {
398
- if (!ms) return 'N/A';
410
+ if (!ms) return "N/A";
399
411
  const seconds = Math.floor(ms / 1000);
400
412
  const minutes = Math.floor(seconds / 60);
401
413
  const hours = Math.floor(minutes / 60);
@@ -412,7 +424,7 @@ class AnalyticsTracker {
412
424
  class WebhookNotifier {
413
425
  constructor(config) {
414
426
  this.url = config.webhookUrl;
415
- this.events = config.webhookEvents || ['complete', 'error'];
427
+ this.events = config.webhookEvents || ["complete", "error"];
416
428
  this.enabled = !!this.url;
417
429
  }
418
430
 
@@ -430,7 +442,7 @@ class WebhookNotifier {
430
442
  const data = JSON.stringify({
431
443
  event,
432
444
  timestamp: new Date().toISOString(),
433
- payload
445
+ payload,
434
446
  });
435
447
 
436
448
  return new Promise((resolve) => {
@@ -438,23 +450,23 @@ class WebhookNotifier {
438
450
  const urlObj = new URL(this.url);
439
451
  const options = {
440
452
  hostname: urlObj.hostname,
441
- port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80),
453
+ port: urlObj.port || (urlObj.protocol === "https:" ? 443 : 80),
442
454
  path: urlObj.pathname + urlObj.search,
443
- method: 'POST',
455
+ method: "POST",
444
456
  headers: {
445
- 'Content-Type': 'application/json',
446
- 'Content-Length': Buffer.byteLength(data),
447
- 'User-Agent': 'ContentCoordinator/1.0'
448
- }
457
+ "Content-Type": "application/json",
458
+ "Content-Length": Buffer.byteLength(data),
459
+ "User-Agent": "ContentCoordinator/1.0",
460
+ },
449
461
  };
450
462
 
451
- const protocol = urlObj.protocol === 'https:' ? https : http;
463
+ const protocol = urlObj.protocol === "https:" ? https : http;
452
464
  const req = protocol.request(options, (res) => {
453
465
  resolve(res.statusCode >= 200 && res.statusCode < 300);
454
466
  });
455
467
 
456
- req.on('error', (err) => {
457
- console.error('[Webhook] Error:', err.message);
468
+ req.on("error", (err) => {
469
+ console.error("[Webhook] Error:", err.message);
458
470
  resolve(false);
459
471
  });
460
472
 
@@ -466,7 +478,7 @@ class WebhookNotifier {
466
478
  req.write(data);
467
479
  req.end();
468
480
  } catch (err) {
469
- console.error('[Webhook] Error:', err.message);
481
+ console.error("[Webhook] Error:", err.message);
470
482
  resolve(false);
471
483
  }
472
484
  });
@@ -477,7 +489,7 @@ class WebhookNotifier {
477
489
  * @param {Object} info - Workflow info
478
490
  */
479
491
  async notifyStart(info) {
480
- await this.notify('start', info);
492
+ await this.notify("start", info);
481
493
  }
482
494
 
483
495
  /**
@@ -485,7 +497,7 @@ class WebhookNotifier {
485
497
  * @param {Object} results - Final results
486
498
  */
487
499
  async notifyComplete(results) {
488
- await this.notify('complete', results);
500
+ await this.notify("complete", results);
489
501
  }
490
502
 
491
503
  /**
@@ -493,7 +505,7 @@ class WebhookNotifier {
493
505
  * @param {Object} error - Error info
494
506
  */
495
507
  async notifyError(error) {
496
- await this.notify('error', error);
508
+ await this.notify("error", error);
497
509
  }
498
510
 
499
511
  /**
@@ -501,7 +513,7 @@ class WebhookNotifier {
501
513
  * @param {Object} item - Published item info
502
514
  */
503
515
  async notifyPublished(item) {
504
- await this.notify('published', item);
516
+ await this.notify("published", item);
505
517
  }
506
518
  }
507
519
 
@@ -512,10 +524,10 @@ class QueueManager {
512
524
  constructor(queuePath) {
513
525
  this.queuePath = queuePath;
514
526
  this.queue = {
515
- version: '1.0',
527
+ version: "1.0",
516
528
  created: null,
517
529
  updated: null,
518
- items: []
530
+ items: [],
519
531
  };
520
532
  }
521
533
 
@@ -525,12 +537,12 @@ class QueueManager {
525
537
  */
526
538
  async load() {
527
539
  try {
528
- const data = await fs.readFile(this.queuePath, 'utf8');
540
+ const data = await fs.readFile(this.queuePath, "utf8");
529
541
  this.queue = JSON.parse(data);
530
542
  return this.queue;
531
543
  } catch (err) {
532
- if (err.code !== 'ENOENT') {
533
- console.error('[QueueManager] Error loading queue:', err.message);
544
+ if (err.code !== "ENOENT") {
545
+ console.error("[QueueManager] Error loading queue:", err.message);
534
546
  }
535
547
  this.queue.created = new Date().toISOString();
536
548
  return this.queue;
@@ -555,11 +567,11 @@ class QueueManager {
555
567
  const queueItem = {
556
568
  id: `item-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
557
569
  addedAt: new Date().toISOString(),
558
- status: 'pending',
559
- priority: item.priority || 'normal',
570
+ status: "pending",
571
+ priority: item.priority || "normal",
560
572
  filePath: item.filePath,
561
573
  metadata: item.metadata || {},
562
- ...item
574
+ ...item,
563
575
  };
564
576
 
565
577
  this.queue.items.push(queueItem);
@@ -573,7 +585,7 @@ class QueueManager {
573
585
  * @returns {boolean} Whether item was removed
574
586
  */
575
587
  async removeItem(id) {
576
- const index = this.queue.items.findIndex(item => item.id === id);
588
+ const index = this.queue.items.findIndex((item) => item.id === id);
577
589
  if (index === -1) return false;
578
590
 
579
591
  this.queue.items.splice(index, 1);
@@ -588,7 +600,7 @@ class QueueManager {
588
600
  * @param {Object} [extra] - Additional data to merge
589
601
  */
590
602
  async updateItemStatus(id, status, extra = {}) {
591
- const item = this.queue.items.find(i => i.id === id);
603
+ const item = this.queue.items.find((i) => i.id === id);
592
604
  if (item) {
593
605
  item.status = status;
594
606
  item.updatedAt = new Date().toISOString();
@@ -604,7 +616,7 @@ class QueueManager {
604
616
  getPendingItems() {
605
617
  const priorityOrder = { high: 0, normal: 1, low: 2 };
606
618
  return this.queue.items
607
- .filter(item => item.status === 'pending')
619
+ .filter((item) => item.status === "pending")
608
620
  .sort((a, b) => {
609
621
  const pA = priorityOrder[a.priority] ?? 1;
610
622
  const pB = priorityOrder[b.priority] ?? 1;
@@ -624,14 +636,15 @@ class QueueManager {
624
636
  processing: 0,
625
637
  completed: 0,
626
638
  failed: 0,
627
- byPriority: { high: 0, normal: 0, low: 0 }
639
+ byPriority: { high: 0, normal: 0, low: 0 },
628
640
  };
629
641
 
630
642
  for (const item of this.queue.items) {
631
- if (item.status === 'pending') stats.pending++;
632
- else if (item.status === 'processing') stats.processing++;
633
- else if (item.status === 'completed' || item.status === 'published') stats.completed++;
634
- else if (item.status === 'failed') stats.failed++;
643
+ if (item.status === "pending") stats.pending++;
644
+ else if (item.status === "processing") stats.processing++;
645
+ else if (item.status === "completed" || item.status === "published")
646
+ stats.completed++;
647
+ else if (item.status === "failed") stats.failed++;
635
648
 
636
649
  if (stats.byPriority[item.priority] !== undefined) {
637
650
  stats.byPriority[item.priority]++;
@@ -648,7 +661,7 @@ class QueueManager {
648
661
  async clearCompleted() {
649
662
  const before = this.queue.items.length;
650
663
  this.queue.items = this.queue.items.filter(
651
- item => !['completed', 'published'].includes(item.status)
664
+ (item) => !["completed", "published"].includes(item.status),
652
665
  );
653
666
  await this.save();
654
667
  return before - this.queue.items.length;
@@ -671,78 +684,80 @@ class PlatformPublisher {
671
684
  initializePlatforms() {
672
685
  // WordPress publisher
673
686
  this.platforms.set(PLATFORMS.WORDPRESS, {
674
- name: 'WordPress',
687
+ name: "WordPress",
675
688
  publish: async (item, publishFn) => {
676
689
  return publishFn(item, {
677
690
  platform: PLATFORMS.WORDPRESS,
678
- endpoint: 'wp-json/wp/v2/posts'
691
+ endpoint: "wp-json/wp/v2/posts",
679
692
  });
680
693
  },
681
694
  validateConfig: () => {
682
- return !!(process.env.WORDPRESS_URL && process.env.WORDPRESS_APP_PASSWORD);
683
- }
695
+ return !!(
696
+ process.env.WORDPRESS_URL && process.env.WORDPRESS_APP_PASSWORD
697
+ );
698
+ },
684
699
  });
685
700
 
686
701
  // PayloadCMS publisher
687
702
  this.platforms.set(PLATFORMS.PAYLOADCMS, {
688
- name: 'PayloadCMS',
703
+ name: "PayloadCMS",
689
704
  publish: async (item, publishFn) => {
690
705
  return publishFn(item, {
691
706
  platform: PLATFORMS.PAYLOADCMS,
692
- endpoint: 'api/posts'
707
+ endpoint: "api/posts",
693
708
  });
694
709
  },
695
710
  validateConfig: () => {
696
711
  return !!(process.env.PAYLOADCMS_URL && process.env.PAYLOADCMS_EMAIL);
697
- }
712
+ },
698
713
  });
699
714
 
700
715
  // Static site generator (writes to file)
701
716
  this.platforms.set(PLATFORMS.STATIC, {
702
- name: 'Static',
717
+ name: "Static",
703
718
  publish: async (item, publishFn) => {
704
719
  return publishFn(item, {
705
720
  platform: PLATFORMS.STATIC,
706
- outputDir: this.config.staticOutputDir || './content'
721
+ outputDir: this.config.staticOutputDir || "./content",
707
722
  });
708
723
  },
709
- validateConfig: () => true
724
+ validateConfig: () => true,
710
725
  });
711
726
 
712
727
  // Docusaurus
713
728
  this.platforms.set(PLATFORMS.DOCUSAURUS, {
714
- name: 'Docusaurus',
729
+ name: "Docusaurus",
715
730
  publish: async (item, publishFn) => {
716
731
  return publishFn(item, {
717
732
  platform: PLATFORMS.DOCUSAURUS,
718
- outputDir: this.config.docusaurusDir || './docs'
733
+ outputDir: this.config.docusaurusDir || "./docs",
719
734
  });
720
735
  },
721
- validateConfig: () => true
736
+ validateConfig: () => true,
722
737
  });
723
738
 
724
739
  // Mintlify
725
740
  this.platforms.set(PLATFORMS.MINTLIFY, {
726
- name: 'Mintlify',
741
+ name: "Mintlify",
727
742
  publish: async (item, publishFn) => {
728
743
  return publishFn(item, {
729
744
  platform: PLATFORMS.MINTLIFY,
730
- outputDir: this.config.mintlifyDir || './docs'
745
+ outputDir: this.config.mintlifyDir || "./docs",
731
746
  });
732
747
  },
733
- validateConfig: () => true
748
+ validateConfig: () => true,
734
749
  });
735
750
 
736
751
  // Astro
737
752
  this.platforms.set(PLATFORMS.ASTRO, {
738
- name: 'Astro',
753
+ name: "Astro",
739
754
  publish: async (item, publishFn) => {
740
755
  return publishFn(item, {
741
756
  platform: PLATFORMS.ASTRO,
742
- outputDir: this.config.astroDir || './src/content'
757
+ outputDir: this.config.astroDir || "./src/content",
743
758
  });
744
759
  },
745
- validateConfig: () => true
760
+ validateConfig: () => true,
746
761
  });
747
762
  }
748
763
 
@@ -752,7 +767,10 @@ class PlatformPublisher {
752
767
  * @returns {Object|null} Platform publisher
753
768
  */
754
769
  getPublisher(platform) {
755
- return this.platforms.get(platform) || this.platforms.get(this.config.defaultPlatform);
770
+ return (
771
+ this.platforms.get(platform) ||
772
+ this.platforms.get(this.config.defaultPlatform)
773
+ );
756
774
  }
757
775
 
758
776
  /**
@@ -805,7 +823,7 @@ class CronHelper {
805
823
  try {
806
824
  // Check if lock file exists and is stale
807
825
  try {
808
- const lockData = await fs.readFile(this.lockPath, 'utf8');
826
+ const lockData = await fs.readFile(this.lockPath, "utf8");
809
827
  const lock = JSON.parse(lockData);
810
828
 
811
829
  // Check if lock is stale (older than 1 hour)
@@ -813,31 +831,35 @@ class CronHelper {
813
831
  const maxAge = 60 * 60 * 1000; // 1 hour
814
832
 
815
833
  if (lockAge > maxAge) {
816
- console.error('[CronHelper] Stale lock detected, removing...');
834
+ console.error("[CronHelper] Stale lock detected, removing...");
817
835
  await fs.unlink(this.lockPath);
818
836
  } else {
819
837
  // Lock is held by another process
820
838
  if (!this.config.quietMode) {
821
- console.log(`[CronHelper] Lock held by PID ${lock.pid} since ${lock.timestamp}`);
839
+ console.log(
840
+ `[CronHelper] Lock held by PID ${lock.pid} since ${lock.timestamp}`,
841
+ );
822
842
  }
823
843
  return false;
824
844
  }
825
845
  } catch (err) {
826
846
  // Lock file doesn't exist, we can proceed
827
- if (err.code !== 'ENOENT') throw err;
847
+ if (err.code !== "ENOENT") throw err;
828
848
  }
829
849
 
830
850
  // Create lock file
831
851
  const lockData = {
832
852
  pid: process.pid,
833
853
  timestamp: new Date().toISOString(),
834
- hostname: os.hostname()
854
+ hostname: os.hostname(),
835
855
  };
836
856
 
837
- await fs.writeFile(this.lockPath, JSON.stringify(lockData, null, 2), { flag: 'wx' });
857
+ await fs.writeFile(this.lockPath, JSON.stringify(lockData, null, 2), {
858
+ flag: "wx",
859
+ });
838
860
  return true;
839
861
  } catch (err) {
840
- if (err.code === 'EEXIST') {
862
+ if (err.code === "EEXIST") {
841
863
  // Another process created the lock first
842
864
  return false;
843
865
  }
@@ -853,8 +875,8 @@ class CronHelper {
853
875
  try {
854
876
  await fs.unlink(this.lockPath);
855
877
  } catch (err) {
856
- if (err.code !== 'ENOENT') {
857
- console.error('[CronHelper] Error releasing lock:', err.message);
878
+ if (err.code !== "ENOENT") {
879
+ console.error("[CronHelper] Error releasing lock:", err.message);
858
880
  }
859
881
  }
860
882
  }
@@ -869,19 +891,21 @@ class CronHelper {
869
891
  }
870
892
 
871
893
  try {
872
- const data = await fs.readFile(this.lastRunPath, 'utf8');
894
+ const data = await fs.readFile(this.lastRunPath, "utf8");
873
895
  const lastRunInfo = JSON.parse(data);
874
896
  const lastRun = new Date(lastRunInfo.timestamp);
875
897
  const elapsed = (Date.now() - lastRun.getTime()) / 1000;
876
898
 
877
899
  if (elapsed < this.config.minRunInterval) {
878
- const nextRunTime = new Date(lastRun.getTime() + this.config.minRunInterval * 1000);
900
+ const nextRunTime = new Date(
901
+ lastRun.getTime() + this.config.minRunInterval * 1000,
902
+ );
879
903
  return { canRun: false, nextRunTime, lastRun };
880
904
  }
881
905
 
882
906
  return { canRun: true, nextRunTime: null, lastRun };
883
907
  } catch (err) {
884
- if (err.code === 'ENOENT') {
908
+ if (err.code === "ENOENT") {
885
909
  // No last run file, first run
886
910
  return { canRun: true, nextRunTime: null, lastRun: null };
887
911
  }
@@ -902,8 +926,8 @@ class CronHelper {
902
926
  total: results.stats?.totalItems || 0,
903
927
  published: results.stats?.published || 0,
904
928
  failed: results.stats?.failed || 0,
905
- duration: results.duration
906
- }
929
+ duration: results.duration,
930
+ },
907
931
  };
908
932
 
909
933
  await fs.writeFile(this.lastRunPath, JSON.stringify(runInfo, null, 2));
@@ -915,10 +939,10 @@ class CronHelper {
915
939
  */
916
940
  async getLastRun() {
917
941
  try {
918
- const data = await fs.readFile(this.lastRunPath, 'utf8');
942
+ const data = await fs.readFile(this.lastRunPath, "utf8");
919
943
  return JSON.parse(data);
920
944
  } catch (err) {
921
- if (err.code === 'ENOENT') return null;
945
+ if (err.code === "ENOENT") return null;
922
946
  throw err;
923
947
  }
924
948
  }
@@ -930,15 +954,16 @@ class CronHelper {
930
954
  */
931
955
  generateCrontabEntry(options = {}) {
932
956
  const {
933
- schedule = '0 */6 * * *', // Default: every 6 hours
957
+ schedule = "0 */6 * * *", // Default: every 6 hours
934
958
  workDir = process.cwd(),
935
- contentDir = './content-queue',
936
- logFile = '/var/log/content-coordinator.log',
937
- user = process.env.USER || 'ubuntu',
938
- extraFlags = ''
959
+ contentDir = "content-queue",
960
+ logFile = "/var/log/content-production-coordinator.log",
961
+ user = process.env.USER || "ubuntu",
962
+ extraFlags = "",
939
963
  } = options;
940
964
 
941
- const command = `cd ${workDir} && /usr/bin/npx myaidev-method coordinate-content ${contentDir} --cron --force ${extraFlags}`.trim();
965
+ const command =
966
+ `cd ${workDir} && /usr/bin/npx myaidev-method coordinate-content ${contentDir} --cron --force ${extraFlags}`.trim();
942
967
 
943
968
  return `# Content Coordinator - Automated publishing
944
969
  # Schedule: ${this.describeCronSchedule(schedule)}
@@ -946,7 +971,7 @@ class CronHelper {
946
971
  ${schedule} ${user} ${command} >> ${logFile} 2>&1
947
972
 
948
973
  # Alternative with flock for extra safety:
949
- # ${schedule} ${user} /usr/bin/flock -n /tmp/content-coordinator.lock ${command} >> ${logFile} 2>&1
974
+ # ${schedule} ${user} /usr/bin/flock -n /tmp/content-production-coordinator.lock ${command} >> ${logFile} 2>&1
950
975
  `;
951
976
  }
952
977
 
@@ -956,23 +981,23 @@ ${schedule} ${user} ${command} >> ${logFile} 2>&1
956
981
  * @returns {string} Human-readable description
957
982
  */
958
983
  describeCronSchedule(schedule) {
959
- const parts = schedule.split(' ');
984
+ const parts = schedule.split(" ");
960
985
  if (parts.length !== 5) return schedule;
961
986
 
962
987
  const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
963
988
 
964
989
  // Common patterns
965
- if (schedule === '0 * * * *') return 'Every hour';
966
- if (schedule === '*/15 * * * *') return 'Every 15 minutes';
967
- if (schedule === '*/30 * * * *') return 'Every 30 minutes';
968
- if (schedule === '0 */2 * * *') return 'Every 2 hours';
969
- if (schedule === '0 */6 * * *') return 'Every 6 hours';
970
- if (schedule === '0 */12 * * *') return 'Every 12 hours';
971
- if (schedule === '0 0 * * *') return 'Daily at midnight';
972
- if (schedule === '0 9 * * *') return 'Daily at 9:00 AM';
973
- if (schedule === '0 9 * * 1-5') return 'Weekdays at 9:00 AM';
974
- if (schedule === '0 0 * * 0') return 'Weekly on Sunday at midnight';
975
- if (schedule === '0 0 1 * *') return 'Monthly on the 1st at midnight';
990
+ if (schedule === "0 * * * *") return "Every hour";
991
+ if (schedule === "*/15 * * * *") return "Every 15 minutes";
992
+ if (schedule === "*/30 * * * *") return "Every 30 minutes";
993
+ if (schedule === "0 */2 * * *") return "Every 2 hours";
994
+ if (schedule === "0 */6 * * *") return "Every 6 hours";
995
+ if (schedule === "0 */12 * * *") return "Every 12 hours";
996
+ if (schedule === "0 0 * * *") return "Daily at midnight";
997
+ if (schedule === "0 9 * * *") return "Daily at 9:00 AM";
998
+ if (schedule === "0 9 * * 1-5") return "Weekdays at 9:00 AM";
999
+ if (schedule === "0 0 * * 0") return "Weekly on Sunday at midnight";
1000
+ if (schedule === "0 0 1 * *") return "Monthly on the 1st at midnight";
976
1001
 
977
1002
  return schedule;
978
1003
  }
@@ -984,35 +1009,35 @@ ${schedule} ${user} ${command} >> ${logFile} 2>&1
984
1009
  getSuggestedSchedules() {
985
1010
  return [
986
1011
  {
987
- name: 'High frequency',
988
- schedule: '*/30 * * * *',
989
- description: 'Every 30 minutes - for time-sensitive content'
1012
+ name: "High frequency",
1013
+ schedule: "*/30 * * * *",
1014
+ description: "Every 30 minutes - for time-sensitive content",
990
1015
  },
991
1016
  {
992
- name: 'Regular',
993
- schedule: '0 */6 * * *',
994
- description: 'Every 6 hours - balanced approach'
1017
+ name: "Regular",
1018
+ schedule: "0 */6 * * *",
1019
+ description: "Every 6 hours - balanced approach",
995
1020
  },
996
1021
  {
997
- name: 'Daily morning',
998
- schedule: '0 9 * * *',
999
- description: 'Daily at 9:00 AM - publish during business hours'
1022
+ name: "Daily morning",
1023
+ schedule: "0 9 * * *",
1024
+ description: "Daily at 9:00 AM - publish during business hours",
1000
1025
  },
1001
1026
  {
1002
- name: 'Weekdays only',
1003
- schedule: '0 9 * * 1-5',
1004
- description: 'Weekdays at 9:00 AM - avoid weekend publishing'
1027
+ name: "Weekdays only",
1028
+ schedule: "0 9 * * 1-5",
1029
+ description: "Weekdays at 9:00 AM - avoid weekend publishing",
1005
1030
  },
1006
1031
  {
1007
- name: 'Twice daily',
1008
- schedule: '0 9,17 * * *',
1009
- description: '9:00 AM and 5:00 PM - morning and evening'
1032
+ name: "Twice daily",
1033
+ schedule: "0 9,17 * * *",
1034
+ description: "9:00 AM and 5:00 PM - morning and evening",
1010
1035
  },
1011
1036
  {
1012
- name: 'Weekly',
1013
- schedule: '0 9 * * 1',
1014
- description: 'Every Monday at 9:00 AM - weekly content drops'
1015
- }
1037
+ name: "Weekly",
1038
+ schedule: "0 9 * * 1",
1039
+ description: "Every Monday at 9:00 AM - weekly content drops",
1040
+ },
1016
1041
  ];
1017
1042
  }
1018
1043
 
@@ -1027,16 +1052,16 @@ ${schedule} ${user} ${command} >> ${logFile} 2>&1
1027
1052
  if (parts.length !== 5) {
1028
1053
  return {
1029
1054
  valid: false,
1030
- error: `Expected 5 fields (minute hour day month weekday), got ${parts.length}`
1055
+ error: `Expected 5 fields (minute hour day month weekday), got ${parts.length}`,
1031
1056
  };
1032
1057
  }
1033
1058
 
1034
1059
  const ranges = [
1035
- { name: 'minute', min: 0, max: 59 },
1036
- { name: 'hour', min: 0, max: 23 },
1037
- { name: 'day of month', min: 1, max: 31 },
1038
- { name: 'month', min: 1, max: 12 },
1039
- { name: 'day of week', min: 0, max: 7 }
1060
+ { name: "minute", min: 0, max: 59 },
1061
+ { name: "hour", min: 0, max: 23 },
1062
+ { name: "day of month", min: 1, max: 31 },
1063
+ { name: "month", min: 1, max: 12 },
1064
+ { name: "day of week", min: 0, max: 7 },
1040
1065
  ];
1041
1066
 
1042
1067
  for (let i = 0; i < 5; i++) {
@@ -1044,32 +1069,47 @@ ${schedule} ${user} ${command} >> ${logFile} 2>&1
1044
1069
  const range = ranges[i];
1045
1070
 
1046
1071
  // Skip wildcards
1047
- if (part === '*') continue;
1072
+ if (part === "*") continue;
1048
1073
 
1049
1074
  // Check step values (*/n)
1050
- if (part.startsWith('*/')) {
1075
+ if (part.startsWith("*/")) {
1051
1076
  const step = parseInt(part.slice(2), 10);
1052
1077
  if (isNaN(step) || step < 1) {
1053
- return { valid: false, error: `Invalid step value in ${range.name}: ${part}` };
1078
+ return {
1079
+ valid: false,
1080
+ error: `Invalid step value in ${range.name}: ${part}`,
1081
+ };
1054
1082
  }
1055
1083
  continue;
1056
1084
  }
1057
1085
 
1058
1086
  // Check ranges (n-m)
1059
- if (part.includes('-')) {
1060
- const [start, end] = part.split('-').map(n => parseInt(n, 10));
1061
- if (isNaN(start) || isNaN(end) || start < range.min || end > range.max || start > end) {
1062
- return { valid: false, error: `Invalid range in ${range.name}: ${part}` };
1087
+ if (part.includes("-")) {
1088
+ const [start, end] = part.split("-").map((n) => parseInt(n, 10));
1089
+ if (
1090
+ isNaN(start) ||
1091
+ isNaN(end) ||
1092
+ start < range.min ||
1093
+ end > range.max ||
1094
+ start > end
1095
+ ) {
1096
+ return {
1097
+ valid: false,
1098
+ error: `Invalid range in ${range.name}: ${part}`,
1099
+ };
1063
1100
  }
1064
1101
  continue;
1065
1102
  }
1066
1103
 
1067
1104
  // Check lists (n,m,...)
1068
- if (part.includes(',')) {
1069
- const values = part.split(',').map(n => parseInt(n, 10));
1105
+ if (part.includes(",")) {
1106
+ const values = part.split(",").map((n) => parseInt(n, 10));
1070
1107
  for (const val of values) {
1071
1108
  if (isNaN(val) || val < range.min || val > range.max) {
1072
- return { valid: false, error: `Invalid value in ${range.name} list: ${val}` };
1109
+ return {
1110
+ valid: false,
1111
+ error: `Invalid value in ${range.name} list: ${val}`,
1112
+ };
1073
1113
  }
1074
1114
  }
1075
1115
  continue;
@@ -1078,7 +1118,10 @@ ${schedule} ${user} ${command} >> ${logFile} 2>&1
1078
1118
  // Check single value
1079
1119
  const val = parseInt(part, 10);
1080
1120
  if (isNaN(val) || val < range.min || val > range.max) {
1081
- return { valid: false, error: `Invalid ${range.name}: ${part} (must be ${range.min}-${range.max})` };
1121
+ return {
1122
+ valid: false,
1123
+ error: `Invalid ${range.name}: ${part} (must be ${range.min}-${range.max})`,
1124
+ };
1082
1125
  }
1083
1126
  }
1084
1127
 
@@ -1110,14 +1153,19 @@ class ScheduleManager {
1110
1153
  calculatePublishDate(item, index, totalItems) {
1111
1154
  // Check if item has explicit publish date in front matter
1112
1155
  if (item.metadata.publish_date || item.metadata.scheduled_date) {
1113
- const explicitDate = new Date(item.metadata.publish_date || item.metadata.scheduled_date);
1156
+ const explicitDate = new Date(
1157
+ item.metadata.publish_date || item.metadata.scheduled_date,
1158
+ );
1114
1159
  if (!isNaN(explicitDate.getTime())) {
1115
1160
  return explicitDate;
1116
1161
  }
1117
1162
  }
1118
1163
 
1119
1164
  // If publish immediately
1120
- if (this.config.defaultPublishDelay === 0 && this.config.publishSpreadInterval === 0) {
1165
+ if (
1166
+ this.config.defaultPublishDelay === 0 &&
1167
+ this.config.publishSpreadInterval === 0
1168
+ ) {
1121
1169
  return new Date(); // Now
1122
1170
  }
1123
1171
 
@@ -1127,7 +1175,9 @@ class ScheduleManager {
1127
1175
 
1128
1176
  // Apply spread interval if configured
1129
1177
  if (this.config.publishSpreadInterval > 0) {
1130
- baseTime.setHours(baseTime.getHours() + (index * this.config.publishSpreadInterval));
1178
+ baseTime.setHours(
1179
+ baseTime.getHours() + index * this.config.publishSpreadInterval,
1180
+ );
1131
1181
  }
1132
1182
 
1133
1183
  return baseTime;
@@ -1142,9 +1192,9 @@ class ScheduleManager {
1142
1192
  const now = new Date();
1143
1193
  // If publish date is in the future (more than 1 minute from now), use 'future' status
1144
1194
  if (publishDate.getTime() > now.getTime() + 60000) {
1145
- return 'future';
1195
+ return "future";
1146
1196
  }
1147
- return 'publish';
1197
+ return "publish";
1148
1198
  }
1149
1199
 
1150
1200
  /**
@@ -1171,8 +1221,8 @@ class ScheduleManager {
1171
1221
  useWordPressScheduling: this.config.useWordPressScheduling,
1172
1222
  publishDate: this.formatForWordPress(publishDate),
1173
1223
  status: status,
1174
- isScheduled: status === 'future',
1175
- scheduledFor: publishDate.toLocaleString()
1224
+ isScheduled: status === "future",
1225
+ scheduledFor: publishDate.toLocaleString(),
1176
1226
  };
1177
1227
  }
1178
1228
 
@@ -1188,7 +1238,7 @@ class ScheduleManager {
1188
1238
  url: result.url,
1189
1239
  scheduledFor: scheduleContext.publishDate,
1190
1240
  status: scheduleContext.status,
1191
- platform: item.metadata.target_platform
1241
+ platform: item.metadata.target_platform,
1192
1242
  });
1193
1243
  }
1194
1244
 
@@ -1197,17 +1247,20 @@ class ScheduleManager {
1197
1247
  * @returns {Object} Scheduling summary
1198
1248
  */
1199
1249
  getSummary() {
1200
- const immediate = this.scheduledPosts.filter(p => p.status === 'publish');
1201
- const future = this.scheduledPosts.filter(p => p.status === 'future');
1250
+ const immediate = this.scheduledPosts.filter((p) => p.status === "publish");
1251
+ const future = this.scheduledPosts.filter((p) => p.status === "future");
1202
1252
 
1203
1253
  return {
1204
1254
  total: this.scheduledPosts.length,
1205
1255
  publishedImmediately: immediate.length,
1206
1256
  scheduledForFuture: future.length,
1207
1257
  posts: this.scheduledPosts,
1208
- nextScheduled: future.length > 0
1209
- ? future.sort((a, b) => new Date(a.scheduledFor) - new Date(b.scheduledFor))[0]
1210
- : null
1258
+ nextScheduled:
1259
+ future.length > 0
1260
+ ? future.sort(
1261
+ (a, b) => new Date(a.scheduledFor) - new Date(b.scheduledFor),
1262
+ )[0]
1263
+ : null,
1211
1264
  };
1212
1265
  }
1213
1266
 
@@ -1235,14 +1288,14 @@ Generated: ${new Date().toISOString()}
1235
1288
  |-------|---------------|----------|
1236
1289
  `;
1237
1290
  const futurePosts = this.scheduledPosts
1238
- .filter(p => p.status === 'future')
1291
+ .filter((p) => p.status === "future")
1239
1292
  .sort((a, b) => new Date(a.scheduledFor) - new Date(b.scheduledFor));
1240
1293
 
1241
1294
  for (const post of futurePosts) {
1242
1295
  const date = new Date(post.scheduledFor).toLocaleString();
1243
1296
  report += `| ${post.title} | ${date} | ${post.platform} |\n`;
1244
1297
  }
1245
- report += '\n';
1298
+ report += "\n";
1246
1299
  }
1247
1300
 
1248
1301
  if (summary.publishedImmediately > 0) {
@@ -1251,7 +1304,9 @@ Generated: ${new Date().toISOString()}
1251
1304
  | Title | URL | Platform |
1252
1305
  |-------|-----|----------|
1253
1306
  `;
1254
- const immediatePosts = this.scheduledPosts.filter(p => p.status === 'publish');
1307
+ const immediatePosts = this.scheduledPosts.filter(
1308
+ (p) => p.status === "publish",
1309
+ );
1255
1310
 
1256
1311
  for (const post of immediatePosts) {
1257
1312
  report += `| ${post.title} | ${post.url} | ${post.platform} |\n`;
@@ -1282,7 +1337,7 @@ class ContentCoordinator {
1282
1337
  this.analytics = new AnalyticsTracker();
1283
1338
  this.webhook = new WebhookNotifier(this.config);
1284
1339
  this.queue = new QueueManager(
1285
- path.join(this.config.outputDir, this.config.queueFileName)
1340
+ path.join(this.config.outputDir, this.config.queueFileName),
1286
1341
  );
1287
1342
  this.publisher = new PlatformPublisher(this.config);
1288
1343
  this.cronHelper = new CronHelper(this.config);
@@ -1293,7 +1348,7 @@ class ContentCoordinator {
1293
1348
  onPhaseChange: null,
1294
1349
  onItemComplete: null,
1295
1350
  onError: null,
1296
- onConfirmationNeeded: null
1351
+ onConfirmationNeeded: null,
1297
1352
  };
1298
1353
  }
1299
1354
 
@@ -1311,12 +1366,12 @@ class ContentCoordinator {
1311
1366
  ready: [],
1312
1367
  needsReview: [],
1313
1368
  failed: [],
1314
- published: []
1369
+ published: [],
1315
1370
  },
1316
1371
  reports: {
1317
1372
  readyForPublishing: null,
1318
1373
  needsReview: null,
1319
- analytics: null
1374
+ analytics: null,
1320
1375
  },
1321
1376
  stats: {
1322
1377
  totalItems: 0,
@@ -1324,11 +1379,11 @@ class ContentCoordinator {
1324
1379
  ready: 0,
1325
1380
  needsReview: 0,
1326
1381
  published: 0,
1327
- failed: 0
1382
+ failed: 0,
1328
1383
  },
1329
1384
  errors: [],
1330
1385
  checkpoints: [],
1331
- contentRulesLoaded: false
1386
+ contentRulesLoaded: false,
1332
1387
  };
1333
1388
  }
1334
1389
 
@@ -1345,15 +1400,18 @@ class ContentCoordinator {
1345
1400
  * @returns {Promise<boolean>} Whether state was loaded
1346
1401
  */
1347
1402
  async loadCheckpoint() {
1348
- const statePath = path.join(this.config.outputDir, this.config.stateFileName);
1403
+ const statePath = path.join(
1404
+ this.config.outputDir,
1405
+ this.config.stateFileName,
1406
+ );
1349
1407
  try {
1350
- const data = await fs.readFile(statePath, 'utf8');
1408
+ const data = await fs.readFile(statePath, "utf8");
1351
1409
  this.state = JSON.parse(data);
1352
- this.log('Loaded checkpoint from', statePath);
1410
+ this.log("Loaded checkpoint from", statePath);
1353
1411
  return true;
1354
1412
  } catch (err) {
1355
- if (err.code !== 'ENOENT') {
1356
- this.logError('Error loading checkpoint:', err.message);
1413
+ if (err.code !== "ENOENT") {
1414
+ this.logError("Error loading checkpoint:", err.message);
1357
1415
  }
1358
1416
  return false;
1359
1417
  }
@@ -1364,19 +1422,22 @@ class ContentCoordinator {
1364
1422
  * @returns {Promise<void>}
1365
1423
  */
1366
1424
  async saveCheckpoint() {
1367
- const statePath = path.join(this.config.outputDir, this.config.stateFileName);
1425
+ const statePath = path.join(
1426
+ this.config.outputDir,
1427
+ this.config.stateFileName,
1428
+ );
1368
1429
  this.state.lastUpdated = new Date().toISOString();
1369
1430
  this.state.checkpoints.push({
1370
1431
  phase: this.state.phase,
1371
1432
  timestamp: this.state.lastUpdated,
1372
- stats: { ...this.state.stats }
1433
+ stats: { ...this.state.stats },
1373
1434
  });
1374
1435
 
1375
1436
  try {
1376
1437
  await fs.writeFile(statePath, JSON.stringify(this.state, null, 2));
1377
- this.log('Saved checkpoint to', statePath);
1438
+ this.log("Saved checkpoint to", statePath);
1378
1439
  } catch (err) {
1379
- this.logError('Error saving checkpoint:', err.message);
1440
+ this.logError("Error saving checkpoint:", err.message);
1380
1441
  }
1381
1442
  }
1382
1443
 
@@ -1385,13 +1446,16 @@ class ContentCoordinator {
1385
1446
  * @returns {Promise<void>}
1386
1447
  */
1387
1448
  async clearCheckpoint() {
1388
- const statePath = path.join(this.config.outputDir, this.config.stateFileName);
1449
+ const statePath = path.join(
1450
+ this.config.outputDir,
1451
+ this.config.stateFileName,
1452
+ );
1389
1453
  try {
1390
1454
  await fs.unlink(statePath);
1391
- this.log('Cleared checkpoint file');
1455
+ this.log("Cleared checkpoint file");
1392
1456
  } catch (err) {
1393
- if (err.code !== 'ENOENT') {
1394
- this.logError('Error clearing checkpoint:', err.message);
1457
+ if (err.code !== "ENOENT") {
1458
+ this.logError("Error clearing checkpoint:", err.message);
1395
1459
  }
1396
1460
  }
1397
1461
  }
@@ -1404,22 +1468,22 @@ class ContentCoordinator {
1404
1468
  // Try default locations if not specified
1405
1469
  const possiblePaths = [
1406
1470
  this.config.contentRulesPath,
1407
- path.join(this.workDir, 'content-rules.md'),
1408
- path.join(this.workDir, '..', 'content-rules.md'),
1409
- path.join(process.cwd(), 'content-rules.md')
1471
+ path.join(this.workDir, "content-rules.md"),
1472
+ path.join(this.workDir, "..", "content-rules.md"),
1473
+ path.join(process.cwd(), "content-rules.md"),
1410
1474
  ].filter(Boolean);
1411
1475
 
1412
1476
  for (const rulesPath of possiblePaths) {
1413
1477
  this.contentRules = new ContentRulesManager(rulesPath);
1414
1478
  const rules = await this.contentRules.load();
1415
1479
  if (rules) {
1416
- this.log('Loaded content rules from', rulesPath);
1480
+ this.log("Loaded content rules from", rulesPath);
1417
1481
  this.state.contentRulesLoaded = true;
1418
1482
  return;
1419
1483
  }
1420
1484
  }
1421
1485
 
1422
- this.log('No content-rules.md found, proceeding without brand voice rules');
1486
+ this.log("No content-rules.md found, proceeding without brand voice rules");
1423
1487
  }
1424
1488
 
1425
1489
  /**
@@ -1428,17 +1492,17 @@ class ContentCoordinator {
1428
1492
  * @returns {Promise<Object>} Parsed content item
1429
1493
  */
1430
1494
  async parseContentFile(filePath) {
1431
- const content = await fs.readFile(filePath, 'utf8');
1495
+ const content = await fs.readFile(filePath, "utf8");
1432
1496
  const item = {
1433
1497
  id: path.basename(filePath, path.extname(filePath)),
1434
1498
  filePath,
1435
1499
  status: ITEM_STATUS.PENDING,
1436
1500
  metadata: {},
1437
- content: '',
1501
+ content: "",
1438
1502
  verification: null,
1439
1503
  publishedUrl: null,
1440
1504
  errors: [],
1441
- timing: {}
1505
+ timing: {},
1442
1506
  };
1443
1507
 
1444
1508
  // Parse YAML front matter
@@ -1449,14 +1513,17 @@ class ContentCoordinator {
1449
1513
  item.content = frontMatterMatch[2].trim();
1450
1514
 
1451
1515
  // Parse front matter fields
1452
- const lines = frontMatter.split('\n');
1516
+ const lines = frontMatter.split("\n");
1453
1517
  let currentKey = null;
1454
1518
  let arrayMode = false;
1455
1519
 
1456
1520
  for (const line of lines) {
1457
1521
  // Check for array continuation
1458
1522
  if (arrayMode && line.match(/^\s+-\s+/)) {
1459
- const value = line.replace(/^\s+-\s+/, '').trim().replace(/^["']|["']$/g, '');
1523
+ const value = line
1524
+ .replace(/^\s+-\s+/, "")
1525
+ .trim()
1526
+ .replace(/^["']|["']$/g, "");
1460
1527
  if (!Array.isArray(item.metadata[currentKey])) {
1461
1528
  item.metadata[currentKey] = [];
1462
1529
  }
@@ -1465,13 +1532,17 @@ class ContentCoordinator {
1465
1532
  }
1466
1533
 
1467
1534
  arrayMode = false;
1468
- const colonIndex = line.indexOf(':');
1535
+ const colonIndex = line.indexOf(":");
1469
1536
  if (colonIndex > 0) {
1470
- const key = line.slice(0, colonIndex).trim().toLowerCase().replace(/\s+/g, '_');
1537
+ const key = line
1538
+ .slice(0, colonIndex)
1539
+ .trim()
1540
+ .toLowerCase()
1541
+ .replace(/\s+/g, "_");
1471
1542
  let value = line.slice(colonIndex + 1).trim();
1472
1543
 
1473
1544
  // Check if this starts an array
1474
- if (value === '' || value === '[]') {
1545
+ if (value === "" || value === "[]") {
1475
1546
  currentKey = key;
1476
1547
  arrayMode = true;
1477
1548
  item.metadata[key] = [];
@@ -1479,8 +1550,10 @@ class ContentCoordinator {
1479
1550
  }
1480
1551
 
1481
1552
  // Remove quotes if present
1482
- if ((value.startsWith('"') && value.endsWith('"')) ||
1483
- (value.startsWith("'") && value.endsWith("'"))) {
1553
+ if (
1554
+ (value.startsWith('"') && value.endsWith('"')) ||
1555
+ (value.startsWith("'") && value.endsWith("'"))
1556
+ ) {
1484
1557
  value = value.slice(1, -1);
1485
1558
  }
1486
1559
 
@@ -1500,31 +1573,86 @@ class ContentCoordinator {
1500
1573
  }
1501
1574
 
1502
1575
  // Set defaults for required fields
1503
- item.metadata.title = item.metadata.title || path.basename(filePath, path.extname(filePath));
1504
- item.metadata.status = item.metadata.status || 'pending';
1505
- item.metadata.target_platform = item.metadata.target_platform || this.config.defaultPlatform;
1506
- item.metadata.priority = item.metadata.priority || 'normal';
1576
+ item.metadata.title =
1577
+ item.metadata.title || path.basename(filePath, path.extname(filePath));
1578
+ item.metadata.status = item.metadata.status || "pending";
1579
+ item.metadata.target_platform =
1580
+ item.metadata.target_platform ||
1581
+ item.metadata.publish ||
1582
+ this.config.defaultPlatform;
1583
+ item.metadata.priority = item.metadata.priority || "normal";
1584
+ item.metadata.mode = item.metadata.mode || "new";
1585
+
1586
+ // Extract job directory context from file path (e.g. content-queue/job20260223_01/)
1587
+ const parentDir = path.basename(path.dirname(filePath));
1588
+ if (/^job\d{8}_\d{2}$/.test(parentDir) || /^job\d{8,14}$/.test(parentDir)) {
1589
+ item.metadata.jobDir = parentDir;
1590
+ }
1507
1591
 
1508
1592
  return item;
1509
1593
  }
1510
1594
 
1511
1595
  /**
1512
- * Discover content files in work directory
1596
+ * Discover content files recursively in work directory and job subdirectories
1513
1597
  * @returns {Promise<string[]>} Array of file paths
1514
1598
  */
1515
1599
  async discoverContentFiles() {
1516
1600
  const files = [];
1517
- const entries = await fs.readdir(this.workDir, { withFileTypes: true });
1601
+ await this._scanDirectory(this.workDir, files);
1602
+ return files.sort((a, b) => {
1603
+ const dirA = path.dirname(a);
1604
+ const dirB = path.dirname(b);
1605
+ if (dirA !== dirB) return dirA.localeCompare(dirB);
1606
+ return a.localeCompare(b);
1607
+ });
1608
+ }
1609
+
1610
+ /**
1611
+ * Recursively scan a directory for markdown content files
1612
+ * @param {string} dir - Directory to scan
1613
+ * @param {string[]} files - Accumulator array
1614
+ * @returns {Promise<void>}
1615
+ */
1616
+ async _scanDirectory(dir, files) {
1617
+ let entries;
1618
+ try {
1619
+ entries = await fs.readdir(dir, { withFileTypes: true });
1620
+ } catch (err) {
1621
+ if (err.code === "ENOENT") return;
1622
+ throw err;
1623
+ }
1624
+
1625
+ const EXCLUDED_DIRS = new Set([
1626
+ "node_modules",
1627
+ ".git",
1628
+ ".claude",
1629
+ ".gemini",
1630
+ ".codex",
1631
+ ".agents",
1632
+ "dist",
1633
+ "build",
1634
+ "coverage",
1635
+ "vendor",
1636
+ "skills",
1637
+ "agents",
1638
+ ".myaidev-method",
1639
+ ".content-session",
1640
+ "platforms",
1641
+ ]);
1518
1642
 
1519
1643
  for (const entry of entries) {
1520
- if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) {
1521
- // Skip system files
1522
- if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
1523
- files.push(path.join(this.workDir, entry.name));
1644
+ if (entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
1645
+
1646
+ const fullPath = path.join(dir, entry.name);
1647
+
1648
+ if (entry.isDirectory()) {
1649
+ if (!EXCLUDED_DIRS.has(entry.name)) {
1650
+ await this._scanDirectory(fullPath, files);
1651
+ }
1652
+ } else if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) {
1653
+ files.push(fullPath);
1524
1654
  }
1525
1655
  }
1526
-
1527
- return files.sort();
1528
1656
  }
1529
1657
 
1530
1658
  /**
@@ -1558,7 +1686,7 @@ class ContentCoordinator {
1558
1686
  status: ITEM_STATUS.FAILED,
1559
1687
  metadata: { title: path.basename(filePath) },
1560
1688
  errors: [err.message],
1561
- timing: {}
1689
+ timing: {},
1562
1690
  });
1563
1691
  }
1564
1692
  }
@@ -1571,7 +1699,7 @@ class ContentCoordinator {
1571
1699
  await this.webhook.notifyStart({
1572
1700
  totalItems: this.state.stats.totalItems,
1573
1701
  workDir: this.workDir,
1574
- contentRulesLoaded: this.state.contentRulesLoaded
1702
+ contentRulesLoaded: this.state.contentRulesLoaded,
1575
1703
  });
1576
1704
 
1577
1705
  await this.saveCheckpoint();
@@ -1587,10 +1715,12 @@ class ContentCoordinator {
1587
1715
  this.analytics.startPhase(PHASES.VERIFY);
1588
1716
 
1589
1717
  const pendingItems = this.state.items.filter(
1590
- item => item.status === ITEM_STATUS.PENDING
1718
+ (item) => item.status === ITEM_STATUS.PENDING,
1591
1719
  );
1592
1720
 
1593
- this.log(`Verifying ${pendingItems.length} items with concurrency ${this.config.concurrency}`);
1721
+ this.log(
1722
+ `Verifying ${pendingItems.length} items with concurrency ${this.config.concurrency}`,
1723
+ );
1594
1724
 
1595
1725
  // Get content rules context for verification
1596
1726
  const rulesContext = this.contentRules.getVerificationContext();
@@ -1599,37 +1729,41 @@ class ContentCoordinator {
1599
1729
  for (let i = 0; i < pendingItems.length; i += this.config.concurrency) {
1600
1730
  const batch = pendingItems.slice(i, i + this.config.concurrency);
1601
1731
 
1602
- await Promise.all(batch.map(async (item) => {
1603
- item.status = ITEM_STATUS.VERIFYING;
1604
- const startTime = Date.now();
1605
-
1606
- try {
1607
- // Pass content rules context to verifier
1608
- const result = await this.withRetry(() => verifyFn(item, rulesContext));
1609
- const duration = Date.now() - startTime;
1610
-
1611
- item.verification = result;
1612
- item.status = ITEM_STATUS.VERIFIED;
1613
- item.timing.verification = duration;
1614
- this.state.stats.verified++;
1615
-
1616
- this.analytics.trackVerification(item, duration);
1617
- this.notifyItemComplete(item, 'verified');
1618
- } catch (err) {
1619
- item.status = ITEM_STATUS.FAILED;
1620
- item.errors.push(err.message);
1621
- item.timing.verification = Date.now() - startTime;
1622
- this.state.stats.failed++;
1623
-
1624
- this.analytics.trackError(item, err, PHASES.VERIFY);
1625
- this.notifyError(item, err);
1626
- await this.webhook.notifyError({
1627
- item: item.metadata.title,
1628
- error: err.message,
1629
- phase: PHASES.VERIFY
1630
- });
1631
- }
1632
- }));
1732
+ await Promise.all(
1733
+ batch.map(async (item) => {
1734
+ item.status = ITEM_STATUS.VERIFYING;
1735
+ const startTime = Date.now();
1736
+
1737
+ try {
1738
+ // Pass content rules context to verifier
1739
+ const result = await this.withRetry(() =>
1740
+ verifyFn(item, rulesContext),
1741
+ );
1742
+ const duration = Date.now() - startTime;
1743
+
1744
+ item.verification = result;
1745
+ item.status = ITEM_STATUS.VERIFIED;
1746
+ item.timing.verification = duration;
1747
+ this.state.stats.verified++;
1748
+
1749
+ this.analytics.trackVerification(item, duration);
1750
+ this.notifyItemComplete(item, "verified");
1751
+ } catch (err) {
1752
+ item.status = ITEM_STATUS.FAILED;
1753
+ item.errors.push(err.message);
1754
+ item.timing.verification = Date.now() - startTime;
1755
+ this.state.stats.failed++;
1756
+
1757
+ this.analytics.trackError(item, err, PHASES.VERIFY);
1758
+ this.notifyError(item, err);
1759
+ await this.webhook.notifyError({
1760
+ item: item.metadata.title,
1761
+ error: err.message,
1762
+ phase: PHASES.VERIFY,
1763
+ });
1764
+ }
1765
+ }),
1766
+ );
1633
1767
 
1634
1768
  await this.saveCheckpoint();
1635
1769
  }
@@ -1656,12 +1790,15 @@ class ContentCoordinator {
1656
1790
  }
1657
1791
 
1658
1792
  const verification = item.verification || {};
1659
- const score = (verification.redundancyScore || '').toLowerCase();
1660
- const recommendation = (verification.recommendation || '').toLowerCase();
1793
+ const score = (verification.redundancyScore || "").toLowerCase();
1794
+ const recommendation = (verification.recommendation || "").toLowerCase();
1661
1795
 
1662
1796
  // Check if ready for publishing
1663
- const isLowRedundancy = [REDUNDANCY_THRESHOLDS.MINIMAL, REDUNDANCY_THRESHOLDS.LOW].includes(score);
1664
- const isProceedRecommended = recommendation.includes('proceed');
1797
+ const isLowRedundancy = [
1798
+ REDUNDANCY_THRESHOLDS.MINIMAL,
1799
+ REDUNDANCY_THRESHOLDS.LOW,
1800
+ ].includes(score);
1801
+ const isProceedRecommended = recommendation.includes("proceed");
1665
1802
 
1666
1803
  if (isLowRedundancy && isProceedRecommended) {
1667
1804
  item.status = ITEM_STATUS.READY;
@@ -1693,23 +1830,26 @@ class ContentCoordinator {
1693
1830
  const readyReport = this.generateReadyReport(timestamp);
1694
1831
  const readyPath = path.join(
1695
1832
  this.config.outputDir,
1696
- `ready-for-publishing-${timestamp}.md`
1833
+ `ready-for-publishing-${timestamp}.md`,
1697
1834
  );
1698
1835
  await fs.writeFile(readyPath, readyReport);
1699
1836
  this.state.reports.readyForPublishing = readyPath;
1700
- this.log('Generated report:', readyPath);
1837
+ this.log("Generated report:", readyPath);
1701
1838
  }
1702
1839
 
1703
1840
  // Generate Needs Review report
1704
- if (this.state.results.needsReview.length > 0 || this.state.results.failed.length > 0) {
1841
+ if (
1842
+ this.state.results.needsReview.length > 0 ||
1843
+ this.state.results.failed.length > 0
1844
+ ) {
1705
1845
  const reviewReport = this.generateNeedsReviewReport(timestamp);
1706
1846
  const reviewPath = path.join(
1707
1847
  this.config.outputDir,
1708
- `needs-review-${timestamp}.md`
1848
+ `needs-review-${timestamp}.md`,
1709
1849
  );
1710
1850
  await fs.writeFile(reviewPath, reviewReport);
1711
1851
  this.state.reports.needsReview = reviewPath;
1712
- this.log('Generated report:', reviewPath);
1852
+ this.log("Generated report:", reviewPath);
1713
1853
  }
1714
1854
 
1715
1855
  // Generate analytics report if enabled
@@ -1717,11 +1857,11 @@ class ContentCoordinator {
1717
1857
  const analyticsReport = this.generateAnalyticsReport(timestamp);
1718
1858
  const analyticsPath = path.join(
1719
1859
  this.config.outputDir,
1720
- `analytics-${timestamp}.md`
1860
+ `analytics-${timestamp}.md`,
1721
1861
  );
1722
1862
  await fs.writeFile(analyticsPath, analyticsReport);
1723
1863
  this.state.reports.analytics = analyticsPath;
1724
- this.log('Generated analytics report:', analyticsPath);
1864
+ this.log("Generated analytics report:", analyticsPath);
1725
1865
  }
1726
1866
 
1727
1867
  this.analytics.endPhase(PHASES.REPORT);
@@ -1740,21 +1880,21 @@ class ContentCoordinator {
1740
1880
  total: this.state.stats.totalItems,
1741
1881
  ready: this.state.stats.ready,
1742
1882
  needsReview: this.state.stats.needsReview,
1743
- failed: this.state.stats.failed
1883
+ failed: this.state.stats.failed,
1744
1884
  },
1745
1885
  reports: this.state.reports,
1746
- readyItems: this.state.results.ready.map(item => ({
1886
+ readyItems: this.state.results.ready.map((item) => ({
1747
1887
  title: item.metadata.title,
1748
1888
  score: item.verification?.redundancyScore,
1749
1889
  recommendation: item.verification?.recommendation,
1750
- platform: item.metadata.target_platform
1890
+ platform: item.metadata.target_platform,
1751
1891
  })),
1752
- needsReviewItems: this.state.results.needsReview.map(item => ({
1892
+ needsReviewItems: this.state.results.needsReview.map((item) => ({
1753
1893
  title: item.metadata.title,
1754
1894
  score: item.verification?.redundancyScore,
1755
- reason: item.verification?.feedback || 'Requires manual review'
1895
+ reason: item.verification?.feedback || "Requires manual review",
1756
1896
  })),
1757
- contentRulesApplied: this.state.contentRulesLoaded
1897
+ contentRulesApplied: this.state.contentRulesLoaded,
1758
1898
  };
1759
1899
  }
1760
1900
 
@@ -1765,7 +1905,7 @@ class ContentCoordinator {
1765
1905
  */
1766
1906
  async phasePublish(publishFn) {
1767
1907
  if (this.config.dryRun) {
1768
- this.log('Dry run mode - skipping publish phase');
1908
+ this.log("Dry run mode - skipping publish phase");
1769
1909
  this.setPhase(PHASES.COMPLETE);
1770
1910
  return;
1771
1911
  }
@@ -1774,99 +1914,110 @@ class ContentCoordinator {
1774
1914
  this.analytics.startPhase(PHASES.PUBLISH);
1775
1915
 
1776
1916
  const readyItems = this.state.results.ready.filter(
1777
- item => item.status === ITEM_STATUS.READY
1917
+ (item) => item.status === ITEM_STATUS.READY,
1778
1918
  );
1779
1919
 
1780
1920
  // Get publishing context from content rules
1781
1921
  const publishContext = this.contentRules.getPublishingContext();
1782
1922
 
1783
- this.log(`Publishing ${readyItems.length} items with concurrency ${this.config.concurrency}`);
1923
+ this.log(
1924
+ `Publishing ${readyItems.length} items with concurrency ${this.config.concurrency}`,
1925
+ );
1784
1926
 
1785
1927
  // Process in batches based on concurrency
1786
1928
  for (let i = 0; i < readyItems.length; i += this.config.concurrency) {
1787
1929
  const batch = readyItems.slice(i, i + this.config.concurrency);
1788
1930
  const batchStartIndex = i;
1789
1931
 
1790
- await Promise.all(batch.map(async (item, batchIndex) => {
1791
- item.status = ITEM_STATUS.PUBLISHING;
1792
- const startTime = Date.now();
1793
- const itemIndex = batchStartIndex + batchIndex;
1794
-
1795
- try {
1796
- // Get platform-specific publisher
1797
- const platform = item.metadata.target_platform || this.config.defaultPlatform;
1798
- const platformPublisher = this.publisher.getPublisher(platform);
1799
-
1800
- // Get scheduling context for WordPress native scheduling
1801
- const scheduleContext = this.scheduler.getSchedulingContext(
1802
- item,
1803
- itemIndex,
1804
- readyItems.length
1805
- );
1806
-
1807
- // Publish with platform context, content rules, and scheduling
1808
- const result = await this.withRetry(() =>
1809
- platformPublisher.publish(item, (item, platformContext) =>
1810
- publishFn(item, {
1811
- ...platformContext,
1812
- contentRules: publishContext,
1813
- scheduling: scheduleContext
1814
- })
1815
- )
1816
- );
1817
-
1818
- const duration = Date.now() - startTime;
1819
-
1820
- item.publishedUrl = result.url;
1821
- item.publishedAt = new Date().toISOString();
1822
- item.scheduledFor = scheduleContext.isScheduled ? scheduleContext.publishDate : null;
1823
- item.status = scheduleContext.isScheduled ? ITEM_STATUS.SCHEDULED : ITEM_STATUS.PUBLISHED;
1824
- item.timing.publishing = duration;
1825
- this.state.results.published.push(item);
1826
- this.state.stats.published++;
1827
-
1828
- // Track in scheduler
1829
- this.scheduler.trackScheduledPost(item, result, scheduleContext);
1830
-
1831
- this.analytics.trackPublishing(item, duration);
1832
- this.notifyItemComplete(item, scheduleContext.isScheduled ? 'scheduled' : 'published');
1833
-
1834
- // Update queue status
1835
- await this.queue.updateItemStatus(item.id, 'published', {
1836
- publishedUrl: result.url,
1837
- publishedAt: item.publishedAt,
1838
- scheduledFor: item.scheduledFor
1839
- });
1840
-
1841
- // Webhook notification
1842
- await this.webhook.notifyPublished({
1843
- title: item.metadata.title,
1844
- url: result.url,
1845
- platform,
1846
- scheduled: scheduleContext.isScheduled,
1847
- scheduledFor: scheduleContext.scheduledFor
1848
- });
1849
-
1850
- } catch (err) {
1851
- item.status = ITEM_STATUS.FAILED;
1852
- item.errors.push(err.message);
1853
- item.timing.publishing = Date.now() - startTime;
1854
- this.state.stats.failed++;
1855
-
1856
- this.analytics.trackError(item, err, PHASES.PUBLISH);
1857
- this.notifyError(item, err);
1858
-
1859
- await this.queue.updateItemStatus(item.id, 'failed', {
1860
- error: err.message
1861
- });
1862
-
1863
- await this.webhook.notifyError({
1864
- item: item.metadata.title,
1865
- error: err.message,
1866
- phase: PHASES.PUBLISH
1867
- });
1868
- }
1869
- }));
1932
+ await Promise.all(
1933
+ batch.map(async (item, batchIndex) => {
1934
+ item.status = ITEM_STATUS.PUBLISHING;
1935
+ const startTime = Date.now();
1936
+ const itemIndex = batchStartIndex + batchIndex;
1937
+
1938
+ try {
1939
+ // Get platform-specific publisher
1940
+ const platform =
1941
+ item.metadata.target_platform || this.config.defaultPlatform;
1942
+ const platformPublisher = this.publisher.getPublisher(platform);
1943
+
1944
+ // Get scheduling context for WordPress native scheduling
1945
+ const scheduleContext = this.scheduler.getSchedulingContext(
1946
+ item,
1947
+ itemIndex,
1948
+ readyItems.length,
1949
+ );
1950
+
1951
+ // Publish with platform context, content rules, and scheduling
1952
+ const result = await this.withRetry(() =>
1953
+ platformPublisher.publish(item, (item, platformContext) =>
1954
+ publishFn(item, {
1955
+ ...platformContext,
1956
+ contentRules: publishContext,
1957
+ scheduling: scheduleContext,
1958
+ }),
1959
+ ),
1960
+ );
1961
+
1962
+ const duration = Date.now() - startTime;
1963
+
1964
+ item.publishedUrl = result.url;
1965
+ item.publishedAt = new Date().toISOString();
1966
+ item.scheduledFor = scheduleContext.isScheduled
1967
+ ? scheduleContext.publishDate
1968
+ : null;
1969
+ item.status = scheduleContext.isScheduled
1970
+ ? ITEM_STATUS.SCHEDULED
1971
+ : ITEM_STATUS.PUBLISHED;
1972
+ item.timing.publishing = duration;
1973
+ this.state.results.published.push(item);
1974
+ this.state.stats.published++;
1975
+
1976
+ // Track in scheduler
1977
+ this.scheduler.trackScheduledPost(item, result, scheduleContext);
1978
+
1979
+ this.analytics.trackPublishing(item, duration);
1980
+ this.notifyItemComplete(
1981
+ item,
1982
+ scheduleContext.isScheduled ? "scheduled" : "published",
1983
+ );
1984
+
1985
+ // Update queue status
1986
+ await this.queue.updateItemStatus(item.id, "published", {
1987
+ publishedUrl: result.url,
1988
+ publishedAt: item.publishedAt,
1989
+ scheduledFor: item.scheduledFor,
1990
+ });
1991
+
1992
+ // Webhook notification
1993
+ await this.webhook.notifyPublished({
1994
+ title: item.metadata.title,
1995
+ url: result.url,
1996
+ platform,
1997
+ scheduled: scheduleContext.isScheduled,
1998
+ scheduledFor: scheduleContext.scheduledFor,
1999
+ });
2000
+ } catch (err) {
2001
+ item.status = ITEM_STATUS.FAILED;
2002
+ item.errors.push(err.message);
2003
+ item.timing.publishing = Date.now() - startTime;
2004
+ this.state.stats.failed++;
2005
+
2006
+ this.analytics.trackError(item, err, PHASES.PUBLISH);
2007
+ this.notifyError(item, err);
2008
+
2009
+ await this.queue.updateItemStatus(item.id, "failed", {
2010
+ error: err.message,
2011
+ });
2012
+
2013
+ await this.webhook.notifyError({
2014
+ item: item.metadata.title,
2015
+ error: err.message,
2016
+ phase: PHASES.PUBLISH,
2017
+ });
2018
+ }
2019
+ }),
2020
+ );
1870
2021
 
1871
2022
  await this.saveCheckpoint();
1872
2023
  }
@@ -1888,18 +2039,26 @@ class ContentCoordinator {
1888
2039
  const lockAcquired = await this.cronHelper.acquireLock();
1889
2040
  if (!lockAcquired) {
1890
2041
  if (!this.config.quietMode) {
1891
- console.log('[ContentCoordinator] Another instance is running, exiting');
2042
+ console.log(
2043
+ "[ContentCoordinator] Another instance is running, exiting",
2044
+ );
1892
2045
  }
1893
- return { skipped: true, reason: 'lock_held' };
2046
+ return { skipped: true, reason: "lock_held" };
1894
2047
  }
1895
2048
 
1896
2049
  const intervalCheck = await this.cronHelper.checkRunInterval();
1897
2050
  if (!intervalCheck.canRun) {
1898
2051
  await this.cronHelper.releaseLock();
1899
2052
  if (!this.config.quietMode) {
1900
- console.log(`[ContentCoordinator] Minimum interval not met, next run at ${intervalCheck.nextRunTime}`);
2053
+ console.log(
2054
+ `[ContentCoordinator] Minimum interval not met, next run at ${intervalCheck.nextRunTime}`,
2055
+ );
1901
2056
  }
1902
- return { skipped: true, reason: 'interval_not_met', nextRunTime: intervalCheck.nextRunTime };
2057
+ return {
2058
+ skipped: true,
2059
+ reason: "interval_not_met",
2060
+ nextRunTime: intervalCheck.nextRunTime,
2061
+ };
1903
2062
  }
1904
2063
  }
1905
2064
 
@@ -1915,26 +2074,44 @@ class ContentCoordinator {
1915
2074
  await this.phaseInitialize();
1916
2075
  }
1917
2076
 
1918
- if (this.state.phase === PHASES.INITIALIZE || this.state.phase === PHASES.VERIFY) {
2077
+ if (
2078
+ this.state.phase === PHASES.INITIALIZE ||
2079
+ this.state.phase === PHASES.VERIFY
2080
+ ) {
1919
2081
  await this.phaseVerify(verify);
1920
2082
  }
1921
2083
 
1922
- if ([PHASES.INITIALIZE, PHASES.VERIFY, PHASES.CATEGORIZE].includes(this.state.phase)) {
2084
+ if (
2085
+ [PHASES.INITIALIZE, PHASES.VERIFY, PHASES.CATEGORIZE].includes(
2086
+ this.state.phase,
2087
+ )
2088
+ ) {
1923
2089
  await this.phaseCategorize();
1924
2090
  }
1925
2091
 
1926
- if ([PHASES.INITIALIZE, PHASES.VERIFY, PHASES.CATEGORIZE, PHASES.REPORT].includes(this.state.phase)) {
2092
+ if (
2093
+ [
2094
+ PHASES.INITIALIZE,
2095
+ PHASES.VERIFY,
2096
+ PHASES.CATEGORIZE,
2097
+ PHASES.REPORT,
2098
+ ].includes(this.state.phase)
2099
+ ) {
1927
2100
  await this.phaseReport();
1928
2101
  }
1929
2102
 
1930
2103
  const notification = await this.phaseNotify();
1931
2104
 
1932
2105
  // Request confirmation if not in force mode (skip in cron mode)
1933
- if (!this.config.force && !this.config.cronMode && this.state.results.ready.length > 0) {
2106
+ if (
2107
+ !this.config.force &&
2108
+ !this.config.cronMode &&
2109
+ this.state.results.ready.length > 0
2110
+ ) {
1934
2111
  if (confirm) {
1935
2112
  const confirmed = await confirm(notification);
1936
2113
  if (!confirmed) {
1937
- this.log('Publishing cancelled by user');
2114
+ this.log("Publishing cancelled by user");
1938
2115
  this.analytics.endWorkflow();
1939
2116
  if (this.config.cronMode) {
1940
2117
  await this.cronHelper.releaseLock();
@@ -1969,14 +2146,13 @@ class ContentCoordinator {
1969
2146
  }
1970
2147
 
1971
2148
  return results;
1972
-
1973
2149
  } catch (err) {
1974
- this.logError('Workflow error:', err.message);
2150
+ this.logError("Workflow error:", err.message);
1975
2151
  this.analytics.endWorkflow();
1976
2152
  await this.saveCheckpoint();
1977
2153
  await this.webhook.notifyError({
1978
2154
  error: err.message,
1979
- phase: this.state.phase
2155
+ phase: this.state.phase,
1980
2156
  });
1981
2157
 
1982
2158
  // Cron mode: release lock on error
@@ -1997,22 +2173,24 @@ class ContentCoordinator {
1997
2173
  success: this.state.stats.failed === 0,
1998
2174
  stats: this.state.stats,
1999
2175
  reports: this.state.reports,
2000
- published: this.state.results.published.map(item => ({
2176
+ published: this.state.results.published.map((item) => ({
2001
2177
  title: item.metadata.title,
2002
2178
  url: item.publishedUrl,
2003
- platform: item.metadata.target_platform
2179
+ platform: item.metadata.target_platform,
2004
2180
  })),
2005
- needsReview: this.state.results.needsReview.map(item => ({
2181
+ needsReview: this.state.results.needsReview.map((item) => ({
2006
2182
  title: item.metadata.title,
2007
- reason: item.verification?.feedback || 'Requires review'
2183
+ reason: item.verification?.feedback || "Requires review",
2008
2184
  })),
2009
- failed: this.state.results.failed.map(item => ({
2185
+ failed: this.state.results.failed.map((item) => ({
2010
2186
  title: item.metadata.title,
2011
- errors: item.errors
2187
+ errors: item.errors,
2012
2188
  })),
2013
2189
  duration: this.calculateDuration(),
2014
- analytics: this.config.enableAnalytics ? this.analytics.getSummary() : null,
2015
- contentRulesApplied: this.state.contentRulesLoaded
2190
+ analytics: this.config.enableAnalytics
2191
+ ? this.analytics.getSummary()
2192
+ : null,
2193
+ contentRulesApplied: this.state.contentRulesLoaded,
2016
2194
  };
2017
2195
  }
2018
2196
 
@@ -2034,7 +2212,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2034
2212
  - Total Items: ${items.length}
2035
2213
  - Total Word Count: ~${Math.round(totalWords / 1000)}K words
2036
2214
  - Average Redundancy Score: ${this.calculateAverageScore(items)}
2037
- - Content Rules Applied: ${this.state.contentRulesLoaded ? 'Yes' : 'No'}
2215
+ - Content Rules Applied: ${this.state.contentRulesLoaded ? "Yes" : "No"}
2038
2216
 
2039
2217
  ## Items
2040
2218
 
@@ -2044,12 +2222,12 @@ Generated: ${this.formatTimestamp(timestamp)}
2044
2222
  const v = item.verification || {};
2045
2223
  report += `### ${index + 1}. ${item.metadata.title}
2046
2224
  - **File**: ${path.basename(item.filePath)}
2047
- - **Redundancy Score**: ${v.redundancyScore || 'N/A'}
2048
- - **Recommendation**: ${v.recommendation || 'N/A'}
2225
+ - **Redundancy Score**: ${v.redundancyScore || "N/A"}
2226
+ - **Recommendation**: ${v.recommendation || "N/A"}
2049
2227
  - **Word Count**: ~${item.content?.split(/\s+/).length || 0} words
2050
- - **Verifier Feedback**: ${v.feedback || 'No feedback provided'}
2051
- - **Target Platform**: ${item.metadata.target_platform || 'wordpress'}
2052
- - **Priority**: ${item.metadata.priority || 'normal'}
2228
+ - **Verifier Feedback**: ${v.feedback || "No feedback provided"}
2229
+ - **Target Platform**: ${item.metadata.target_platform || "wordpress"}
2230
+ - **Priority**: ${item.metadata.priority || "normal"}
2053
2231
 
2054
2232
  `;
2055
2233
  });
@@ -2084,10 +2262,10 @@ Generated: ${this.formatTimestamp(timestamp)}
2084
2262
  const v = item.verification || {};
2085
2263
  report += `### ${index + 1}. ${item.metadata.title}
2086
2264
  - **File**: ${path.basename(item.filePath)}
2087
- - **Redundancy Score**: ${v.redundancyScore || 'N/A'}
2088
- - **Recommendation**: ${v.recommendation || 'N/A'}
2265
+ - **Redundancy Score**: ${v.redundancyScore || "N/A"}
2266
+ - **Recommendation**: ${v.recommendation || "N/A"}
2089
2267
  - **Issue**: ${this.getReviewReason(item)}
2090
- - **Verifier Feedback**: ${v.feedback || 'No feedback provided'}
2268
+ - **Verifier Feedback**: ${v.feedback || "No feedback provided"}
2091
2269
  - **Suggested Actions**:
2092
2270
  - Review and revise proprietary content
2093
2271
  - Ensure content adds unique value
@@ -2105,7 +2283,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2105
2283
  report += `### ${index + 1}. ${item.metadata.title}
2106
2284
  - **File**: ${path.basename(item.filePath)}
2107
2285
  - **Errors**:
2108
- ${item.errors.map(e => ` - ${e}`).join('\n')}
2286
+ ${item.errors.map((e) => ` - ${e}`).join("\n")}
2109
2287
  - **Suggested Actions**:
2110
2288
  - Review file format and content
2111
2289
  - Fix any parsing errors
@@ -2182,15 +2360,17 @@ Generated: ${this.formatTimestamp(timestamp)}
2182
2360
  */
2183
2361
  getReviewReason(item) {
2184
2362
  const v = item.verification || {};
2185
- const score = (v.redundancyScore || '').toLowerCase();
2186
- const rec = (v.recommendation || '').toLowerCase();
2363
+ const score = (v.redundancyScore || "").toLowerCase();
2364
+ const rec = (v.recommendation || "").toLowerCase();
2187
2365
 
2188
- if (score === 'high') return 'High redundancy - content may duplicate existing knowledge';
2189
- if (score === 'medium') return 'Medium redundancy - consider adding more unique insights';
2190
- if (rec.includes('reject')) return 'Content rejected by verifier';
2191
- if (rec.includes('review')) return 'Manual review recommended';
2366
+ if (score === "high")
2367
+ return "High redundancy - content may duplicate existing knowledge";
2368
+ if (score === "medium")
2369
+ return "Medium redundancy - consider adding more unique insights";
2370
+ if (rec.includes("reject")) return "Content rejected by verifier";
2371
+ if (rec.includes("review")) return "Manual review recommended";
2192
2372
 
2193
- return 'Content requires attention before publishing';
2373
+ return "Content requires attention before publishing";
2194
2374
  }
2195
2375
 
2196
2376
  /**
@@ -2204,20 +2384,20 @@ Generated: ${this.formatTimestamp(timestamp)}
2204
2384
  let count = 0;
2205
2385
 
2206
2386
  for (const item of items) {
2207
- const score = (item.verification?.redundancyScore || '').toLowerCase();
2387
+ const score = (item.verification?.redundancyScore || "").toLowerCase();
2208
2388
  if (scores[score]) {
2209
2389
  total += scores[score];
2210
2390
  count++;
2211
2391
  }
2212
2392
  }
2213
2393
 
2214
- if (count === 0) return 'N/A';
2394
+ if (count === 0) return "N/A";
2215
2395
 
2216
2396
  const avg = total / count;
2217
- if (avg <= 1.5) return 'Minimal';
2218
- if (avg <= 2.5) return 'Low';
2219
- if (avg <= 3.5) return 'Medium';
2220
- return 'High';
2397
+ if (avg <= 1.5) return "Minimal";
2398
+ if (avg <= 2.5) return "Low";
2399
+ if (avg <= 3.5) return "Medium";
2400
+ return "High";
2221
2401
  }
2222
2402
 
2223
2403
  /**
@@ -2234,7 +2414,10 @@ Generated: ${this.formatTimestamp(timestamp)}
2234
2414
  } catch (err) {
2235
2415
  lastError = err;
2236
2416
  if (attempt < this.config.retryAttempts) {
2237
- this.log(`Retry attempt ${attempt}/${this.config.retryAttempts} after error:`, err.message);
2417
+ this.log(
2418
+ `Retry attempt ${attempt}/${this.config.retryAttempts} after error:`,
2419
+ err.message,
2420
+ );
2238
2421
  await this.sleep(this.config.retryDelay * attempt);
2239
2422
  }
2240
2423
  }
@@ -2282,7 +2465,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2282
2465
  this.state.errors.push({
2283
2466
  item: item.metadata.title,
2284
2467
  error: error.message,
2285
- timestamp: new Date().toISOString()
2468
+ timestamp: new Date().toISOString(),
2286
2469
  });
2287
2470
 
2288
2471
  if (this.callbacks.onError) {
@@ -2296,10 +2479,11 @@ Generated: ${this.formatTimestamp(timestamp)}
2296
2479
  */
2297
2480
  getTimestamp() {
2298
2481
  const now = new Date();
2299
- return now.toISOString()
2300
- .replace(/T/, '-')
2301
- .replace(/:/g, '-')
2302
- .replace(/\..+/, '');
2482
+ return now
2483
+ .toISOString()
2484
+ .replace(/T/, "-")
2485
+ .replace(/:/g, "-")
2486
+ .replace(/\..+/, "");
2303
2487
  }
2304
2488
 
2305
2489
  /**
@@ -2308,7 +2492,9 @@ Generated: ${this.formatTimestamp(timestamp)}
2308
2492
  * @returns {string} Human-readable timestamp
2309
2493
  */
2310
2494
  formatTimestamp(timestamp) {
2311
- return timestamp.replace(/-/g, (m, i) => i > 9 ? ':' : '-').replace('-', ' ');
2495
+ return timestamp
2496
+ .replace(/-/g, (m, i) => (i > 9 ? ":" : "-"))
2497
+ .replace("-", " ");
2312
2498
  }
2313
2499
 
2314
2500
  /**
@@ -2316,7 +2502,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2316
2502
  * @returns {string} Duration string
2317
2503
  */
2318
2504
  calculateDuration() {
2319
- if (!this.state.startedAt) return 'N/A';
2505
+ if (!this.state.startedAt) return "N/A";
2320
2506
 
2321
2507
  const start = new Date(this.state.startedAt);
2322
2508
  const end = new Date();
@@ -2337,7 +2523,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2337
2523
  * @returns {Promise<void>}
2338
2524
  */
2339
2525
  sleep(ms) {
2340
- return new Promise(resolve => setTimeout(resolve, ms));
2526
+ return new Promise((resolve) => setTimeout(resolve, ms));
2341
2527
  }
2342
2528
 
2343
2529
  /**
@@ -2346,7 +2532,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2346
2532
  */
2347
2533
  log(...args) {
2348
2534
  if (this.config.verbose) {
2349
- console.log('[ContentCoordinator]', ...args);
2535
+ console.log("[ContentCoordinator]", ...args);
2350
2536
  }
2351
2537
  }
2352
2538
 
@@ -2355,7 +2541,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2355
2541
  * @param {...*} args - Error arguments
2356
2542
  */
2357
2543
  logError(...args) {
2358
- console.error('[ContentCoordinator Error]', ...args);
2544
+ console.error("[ContentCoordinator Error]", ...args);
2359
2545
  }
2360
2546
 
2361
2547
  // ============================================
@@ -2416,7 +2602,7 @@ Generated: ${this.formatTimestamp(timestamp)}
2416
2602
  generateCrontabEntry(options = {}) {
2417
2603
  return this.cronHelper.generateCrontabEntry({
2418
2604
  workDir: this.workDir || process.cwd(),
2419
- ...options
2605
+ ...options,
2420
2606
  });
2421
2607
  }
2422
2608
 
@@ -2506,10 +2692,14 @@ ${entry}
2506
2692
 
2507
2693
  ## Suggested Schedules
2508
2694
 
2509
- ${schedules.map(s => `### ${s.name}
2695
+ ${schedules
2696
+ .map(
2697
+ (s) => `### ${s.name}
2510
2698
  # ${s.description}
2511
2699
  # Schedule: ${s.schedule}
2512
- `).join('\n')}
2700
+ `,
2701
+ )
2702
+ .join("\n")}
2513
2703
 
2514
2704
  ## Environment Variables
2515
2705
 
@@ -2520,9 +2710,9 @@ Make sure these are set in your crontab or script:
2520
2710
 
2521
2711
  ## Log Rotation (optional)
2522
2712
 
2523
- Add to /etc/logrotate.d/content-coordinator:
2713
+ Add to /etc/logrotate.d/content-production-coordinator:
2524
2714
  \`\`\`
2525
- /var/log/content-coordinator.log {
2715
+ /var/log/content-production-coordinator.log {
2526
2716
  weekly
2527
2717
  rotate 4
2528
2718
  compress
@@ -2537,7 +2727,7 @@ After adding, verify with:
2537
2727
  crontab -l
2538
2728
 
2539
2729
  Check logs at:
2540
- tail -f /var/log/content-coordinator.log
2730
+ tail -f /var/log/content-production-coordinator.log
2541
2731
  `;
2542
2732
  }
2543
2733
  }
@@ -2556,7 +2746,7 @@ export {
2556
2746
  ITEM_STATUS,
2557
2747
  REDUNDANCY_THRESHOLDS,
2558
2748
  PLATFORMS,
2559
- DEFAULT_CONFIG
2749
+ DEFAULT_CONFIG,
2560
2750
  };
2561
2751
 
2562
2752
  // Default export for convenience