create-hq 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/dist/deps.d.ts +4 -0
  2. package/dist/deps.d.ts.map +1 -0
  3. package/dist/deps.js +65 -0
  4. package/dist/deps.js.map +1 -0
  5. package/dist/git.d.ts +3 -0
  6. package/dist/git.d.ts.map +1 -0
  7. package/dist/git.js +19 -0
  8. package/dist/git.js.map +1 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +23 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/scaffold.d.ts +8 -0
  14. package/dist/scaffold.d.ts.map +1 -0
  15. package/dist/scaffold.js +130 -0
  16. package/dist/scaffold.js.map +1 -0
  17. package/dist/ui.d.ts +7 -0
  18. package/dist/ui.d.ts.map +1 -0
  19. package/dist/ui.js +36 -0
  20. package/dist/ui.js.map +1 -0
  21. package/package.json +41 -0
  22. package/template/.claude/CLAUDE.md +202 -0
  23. package/template/.claude/commands/checkpoint.md +127 -0
  24. package/template/.claude/commands/cleanup.md +307 -0
  25. package/template/.claude/commands/execute-task.md +440 -0
  26. package/template/.claude/commands/exit-plan.md +41 -0
  27. package/template/.claude/commands/handoff.md +97 -0
  28. package/template/.claude/commands/learn.md +218 -0
  29. package/template/.claude/commands/metrics.md +118 -0
  30. package/template/.claude/commands/newworker.md +162 -0
  31. package/template/.claude/commands/nexttask.md +67 -0
  32. package/template/.claude/commands/prd.md +238 -0
  33. package/template/.claude/commands/reanchor.md +51 -0
  34. package/template/.claude/commands/remember.md +126 -0
  35. package/template/.claude/commands/run-project.md +348 -0
  36. package/template/.claude/commands/run.md +110 -0
  37. package/template/.claude/commands/search-reindex.md +62 -0
  38. package/template/.claude/commands/search.md +100 -0
  39. package/template/.claude/commands/setup.md +381 -0
  40. package/template/.claude/scripts/pure-ralph-loop.ps1 +312 -0
  41. package/template/.claude/scripts/pure-ralph-loop.sh +859 -0
  42. package/template/CHANGELOG.md +220 -0
  43. package/template/LICENSE +21 -0
  44. package/template/MIGRATION.md +259 -0
  45. package/template/README.md +368 -0
  46. package/template/data/journal/.gitkeep +0 -0
  47. package/template/docs/images/ascii-banner-options.md +122 -0
  48. package/template/docs/images/hq-banner.svg +105 -0
  49. package/template/knowledge/Ralph/01-overview.md +71 -0
  50. package/template/knowledge/Ralph/02-core-concepts.md +114 -0
  51. package/template/knowledge/Ralph/03-how-ralph-works.md +184 -0
  52. package/template/knowledge/Ralph/04-back-pressure.md +222 -0
  53. package/template/knowledge/Ralph/05-specifications.md +210 -0
  54. package/template/knowledge/Ralph/06-agents-md.md +222 -0
  55. package/template/knowledge/Ralph/07-implementation.md +316 -0
  56. package/template/knowledge/Ralph/08-economics.md +182 -0
  57. package/template/knowledge/Ralph/09-resources.md +145 -0
  58. package/template/knowledge/Ralph/10-claude-code-workflow.md +212 -0
  59. package/template/knowledge/Ralph/11-team-training-guide.md +383 -0
  60. package/template/knowledge/Ralph/README.md +40 -0
  61. package/template/knowledge/ai-security-framework/CONTRIBUTING.md +139 -0
  62. package/template/knowledge/ai-security-framework/GLOSSARY.md +176 -0
  63. package/template/knowledge/ai-security-framework/LICENSE +21 -0
  64. package/template/knowledge/ai-security-framework/QUICK-START.md +172 -0
  65. package/template/knowledge/ai-security-framework/README.md +232 -0
  66. package/template/knowledge/ai-security-framework/checklists/browser-security.md +301 -0
  67. package/template/knowledge/ai-security-framework/checklists/credential-isolation.md +322 -0
  68. package/template/knowledge/ai-security-framework/checklists/incident-response.md +288 -0
  69. package/template/knowledge/ai-security-framework/checklists/pre-flight.md +249 -0
  70. package/template/knowledge/ai-security-framework/checklists/weekly-audit.md +159 -0
  71. package/template/knowledge/ai-security-framework/configs/audit-logging.md +372 -0
  72. package/template/knowledge/ai-security-framework/configs/kill-switches.md +354 -0
  73. package/template/knowledge/ai-security-framework/docs/01-core-principles.md +256 -0
  74. package/template/knowledge/ai-security-framework/docs/02-threat-landscape.md +326 -0
  75. package/template/knowledge/ai-security-framework/docs/03-security-posture.md +250 -0
  76. package/template/knowledge/ai-security-framework/templates/agents-security.md +233 -0
  77. package/template/knowledge/design-styles/README.md +42 -0
  78. package/template/knowledge/design-styles/american-industrial.md +136 -0
  79. package/template/knowledge/design-styles/ethereal-abstract.md +133 -0
  80. package/template/knowledge/design-styles/liminal-portal.md +111 -0
  81. package/template/knowledge/design-styles/swipes/american-industrial/G-3m4YPW0AADdu2.jpeg +0 -0
  82. package/template/knowledge/design-styles/swipes/american-industrial/G-JJlt5WwAABK3K.png +0 -0
  83. package/template/knowledge/design-styles/swipes/american-industrial/G-JJmj5W0AEbJ-7.png +0 -0
  84. package/template/knowledge/design-styles/swipes/american-industrial/G59fgNuXkAAKLJQ (1).jpeg +0 -0
  85. package/template/knowledge/design-styles/swipes/american-industrial/G59fgNuXkAAKLJQ.jpeg +0 -0
  86. package/template/knowledge/design-styles/swipes/american-industrial/G7fVkn3WEAAM-ST.jpeg +0 -0
  87. package/template/knowledge/design-styles/swipes/american-industrial/G8ECO5JWEAIksyn.png +0 -0
  88. package/template/knowledge/design-styles/swipes/american-industrial/G9-3GQSWoAA8eqZ.png +0 -0
  89. package/template/knowledge/design-styles/swipes/american-industrial/G9xEOqrXkAEZRcs.png +0 -0
  90. package/template/knowledge/design-styles/swipes/american-industrial/G_MVeJrXQAA8sx4.jpeg +0 -0
  91. package/template/knowledge/design-styles/swipes/american-industrial/G_RSkmGXkAAgAVZ.png +0 -0
  92. package/template/knowledge/design-styles/swipes/american-industrial/README.md +31 -0
  93. package/template/knowledge/design-styles/swipes/american-industrial/qyqtg7Dq.png +0 -0
  94. package/template/knowledge/dev-team/README.md +35 -0
  95. package/template/knowledge/dev-team/patterns/README.md +34 -0
  96. package/template/knowledge/dev-team/patterns/frontend/react-best-practices.md +178 -0
  97. package/template/knowledge/dev-team/troubleshooting/README.md +31 -0
  98. package/template/knowledge/dev-team/workflows/README.md +49 -0
  99. package/template/knowledge/hq/checkpoint-schema.json +51 -0
  100. package/template/knowledge/hq/index-md-spec.md +74 -0
  101. package/template/knowledge/hq/thread-schema.md +153 -0
  102. package/template/knowledge/hq-core/checkpoint-schema.json +51 -0
  103. package/template/knowledge/hq-core/index-md-spec.md +74 -0
  104. package/template/knowledge/hq-core/thread-schema.md +153 -0
  105. package/template/knowledge/loom/README.md +51 -0
  106. package/template/knowledge/loom/architecture.md +125 -0
  107. package/template/knowledge/loom/code-style.md +169 -0
  108. package/template/knowledge/loom/llm-proxy.md +132 -0
  109. package/template/knowledge/loom/state-machine.md +131 -0
  110. package/template/knowledge/loom/thread-system.md +117 -0
  111. package/template/knowledge/loom/tools.md +94 -0
  112. package/template/knowledge/loom/weaver.md +96 -0
  113. package/template/knowledge/loom/web-frontend.md +131 -0
  114. package/template/knowledge/projects/README.md +72 -0
  115. package/template/knowledge/projects/templates/README.template.md +28 -0
  116. package/template/knowledge/workers/README.md +195 -0
  117. package/template/knowledge/workers/ralph-loop-pattern.md +157 -0
  118. package/template/knowledge/workers/skill-schema.md +182 -0
  119. package/template/knowledge/workers/state-machine.md +102 -0
  120. package/template/knowledge/workers/templates/base-worker.yaml +73 -0
  121. package/template/knowledge/workers/templates/code-worker.yaml +85 -0
  122. package/template/knowledge/workers/templates/skill.yaml +49 -0
  123. package/template/knowledge/workers/templates/social-worker.yaml +70 -0
  124. package/template/modules/examples/full-manifest.yaml +92 -0
  125. package/template/modules/examples/minimal.yaml +14 -0
  126. package/template/modules/modules.yaml +59 -0
  127. package/template/projects/.gitkeep +0 -0
  128. package/template/projects/incorporate-workers-into-pure-ralph/prd.json +88 -0
  129. package/template/projects/pure-ralph-branch-isolation/README.md +114 -0
  130. package/template/projects/pure-ralph-branch-isolation/prd.json +123 -0
  131. package/template/projects/purist-ralph-loop/README.md +148 -0
  132. package/template/projects/purist-ralph-loop/prd.json +135 -0
  133. package/template/projects/ralph-test/prd.json +50 -0
  134. package/template/prompts/pure-ralph-base.md +551 -0
  135. package/template/settings/.gitkeep +0 -0
  136. package/template/settings/pure-ralph.json +42 -0
  137. package/template/social-content/drafts/INDEX.md +21 -0
  138. package/template/social-content/drafts/linkedin/.gitkeep +1 -0
  139. package/template/social-content/drafts/x/.gitkeep +1 -0
  140. package/template/social-content/images/.gitkeep +1 -0
  141. package/template/starter-projects/code-worker/README.md +97 -0
  142. package/template/starter-projects/code-worker/prd.json +45 -0
  143. package/template/starter-projects/personal-assistant/README.md +42 -0
  144. package/template/starter-projects/personal-assistant/prd.json +43 -0
  145. package/template/starter-projects/social-media/README.md +60 -0
  146. package/template/starter-projects/social-media/prd.json +43 -0
  147. package/template/workers/content-brand/README.md +59 -0
  148. package/template/workers/content-brand/skills/messaging-alignment.md +91 -0
  149. package/template/workers/content-brand/skills/tone-check.md +76 -0
  150. package/template/workers/content-brand/skills/voice-analysis.md +68 -0
  151. package/template/workers/content-brand/worker.yaml +81 -0
  152. package/template/workers/content-legal/README.md +80 -0
  153. package/template/workers/content-legal/skills/claim-substantiation.md +150 -0
  154. package/template/workers/content-legal/skills/compliance-scan.md +123 -0
  155. package/template/workers/content-legal/skills/disclaimer-check.md +146 -0
  156. package/template/workers/content-legal/worker.yaml +118 -0
  157. package/template/workers/content-product/README.md +77 -0
  158. package/template/workers/content-product/skills/claim-verification.md +96 -0
  159. package/template/workers/content-product/skills/feature-accuracy.md +117 -0
  160. package/template/workers/content-product/skills/stats-check.md +128 -0
  161. package/template/workers/content-product/worker.yaml +97 -0
  162. package/template/workers/content-sales/README.md +70 -0
  163. package/template/workers/content-sales/skills/conversion-analysis.md +96 -0
  164. package/template/workers/content-sales/skills/cta-audit.md +107 -0
  165. package/template/workers/content-sales/skills/value-prop-check.md +114 -0
  166. package/template/workers/content-sales/worker.yaml +93 -0
  167. package/template/workers/content-shared/cli.ts +242 -0
  168. package/template/workers/content-shared/index.ts +234 -0
  169. package/template/workers/content-shared/lib/accuracy-analyzer.ts +661 -0
  170. package/template/workers/content-shared/lib/analyze.ts +370 -0
  171. package/template/workers/content-shared/lib/brand-analyzer.ts +526 -0
  172. package/template/workers/content-shared/lib/cms-integration.ts +446 -0
  173. package/template/workers/content-shared/lib/compliance-analyzer.ts +655 -0
  174. package/template/workers/content-shared/lib/conversion-analyzer.ts +555 -0
  175. package/template/workers/content-shared/lib/github-integration.ts +582 -0
  176. package/template/workers/content-shared/lib/output.ts +373 -0
  177. package/template/workers/content-shared/lib/parser.ts +771 -0
  178. package/template/workers/content-shared/lib/priority.ts +439 -0
  179. package/template/workers/content-shared/lib/recommendations.ts +512 -0
  180. package/template/workers/content-shared/lib/reporter.ts +749 -0
  181. package/template/workers/content-shared/lib/restructure.ts +664 -0
  182. package/template/workers/content-shared/lib/scorer.ts +140 -0
  183. package/template/workers/content-shared/lib/types.ts +227 -0
  184. package/template/workers/content-shared/lib/variants.ts +595 -0
  185. package/template/workers/content-shared/package.json +51 -0
  186. package/template/workers/content-shared/pnpm-lock.yaml +39 -0
  187. package/template/workers/content-shared/test/sample-page.json +115 -0
  188. package/template/workers/content-shared/tsconfig.json +20 -0
  189. package/template/workers/dev-team/README.md +166 -0
  190. package/template/workers/dev-team/_template.yaml +70 -0
  191. package/template/workers/dev-team/architect/package.json +27 -0
  192. package/template/workers/dev-team/architect/skills/api-design.md +89 -0
  193. package/template/workers/dev-team/architect/skills/refactor-plan.md +96 -0
  194. package/template/workers/dev-team/architect/skills/system-design.md +100 -0
  195. package/template/workers/dev-team/architect/src/index.ts +49 -0
  196. package/template/workers/dev-team/architect/src/mcp-server.ts +122 -0
  197. package/template/workers/dev-team/architect/src/skills/api-design.ts +316 -0
  198. package/template/workers/dev-team/architect/src/skills/refactor-plan.ts +264 -0
  199. package/template/workers/dev-team/architect/src/skills/system-design.ts +212 -0
  200. package/template/workers/dev-team/architect/tsconfig.json +19 -0
  201. package/template/workers/dev-team/architect/worker.yaml +128 -0
  202. package/template/workers/dev-team/backend-dev/package-lock.json +1252 -0
  203. package/template/workers/dev-team/backend-dev/package.json +27 -0
  204. package/template/workers/dev-team/backend-dev/skills/implement-endpoint.md +70 -0
  205. package/template/workers/dev-team/backend-dev/skills/implement-service.md +62 -0
  206. package/template/workers/dev-team/backend-dev/src/index.ts +51 -0
  207. package/template/workers/dev-team/backend-dev/src/mcp-server.ts +109 -0
  208. package/template/workers/dev-team/backend-dev/src/skills/implement-endpoint.ts +122 -0
  209. package/template/workers/dev-team/backend-dev/src/skills/implement-service.ts +126 -0
  210. package/template/workers/dev-team/backend-dev/tsconfig.json +19 -0
  211. package/template/workers/dev-team/backend-dev/worker.yaml +128 -0
  212. package/template/workers/dev-team/code-reviewer/package-lock.json +1080 -0
  213. package/template/workers/dev-team/code-reviewer/package.json +24 -0
  214. package/template/workers/dev-team/code-reviewer/skills/merge-to-production.md +61 -0
  215. package/template/workers/dev-team/code-reviewer/skills/merge-to-staging.md +54 -0
  216. package/template/workers/dev-team/code-reviewer/skills/request-changes.md +63 -0
  217. package/template/workers/dev-team/code-reviewer/skills/review-pr.md +77 -0
  218. package/template/workers/dev-team/code-reviewer/src/index.ts +56 -0
  219. package/template/workers/dev-team/code-reviewer/src/mcp-server.ts +101 -0
  220. package/template/workers/dev-team/code-reviewer/tsconfig.json +19 -0
  221. package/template/workers/dev-team/code-reviewer/worker.yaml +90 -0
  222. package/template/workers/dev-team/database-dev/package.json +22 -0
  223. package/template/workers/dev-team/database-dev/skills/create-schema.md +48 -0
  224. package/template/workers/dev-team/database-dev/src/index.ts +50 -0
  225. package/template/workers/dev-team/database-dev/src/mcp-server.ts +76 -0
  226. package/template/workers/dev-team/database-dev/tsconfig.json +18 -0
  227. package/template/workers/dev-team/database-dev/worker.yaml +90 -0
  228. package/template/workers/dev-team/frontend-dev/package.json +22 -0
  229. package/template/workers/dev-team/frontend-dev/skills/create-component.md +26 -0
  230. package/template/workers/dev-team/frontend-dev/src/index.ts +50 -0
  231. package/template/workers/dev-team/frontend-dev/src/mcp-server.ts +77 -0
  232. package/template/workers/dev-team/frontend-dev/tsconfig.json +18 -0
  233. package/template/workers/dev-team/frontend-dev/worker.yaml +132 -0
  234. package/template/workers/dev-team/infra-dev/package.json +24 -0
  235. package/template/workers/dev-team/infra-dev/skills/add-monitoring.md +73 -0
  236. package/template/workers/dev-team/infra-dev/skills/configure-deployment.md +80 -0
  237. package/template/workers/dev-team/infra-dev/skills/create-dockerfile.md +62 -0
  238. package/template/workers/dev-team/infra-dev/skills/setup-cicd.md +63 -0
  239. package/template/workers/dev-team/infra-dev/src/index.ts +55 -0
  240. package/template/workers/dev-team/infra-dev/src/mcp-server.ts +82 -0
  241. package/template/workers/dev-team/infra-dev/tsconfig.json +19 -0
  242. package/template/workers/dev-team/infra-dev/worker.yaml +92 -0
  243. package/template/workers/dev-team/knowledge-curator/package.json +24 -0
  244. package/template/workers/dev-team/knowledge-curator/skills/curate-troubleshooting.md +63 -0
  245. package/template/workers/dev-team/knowledge-curator/skills/process-learnings.md +61 -0
  246. package/template/workers/dev-team/knowledge-curator/skills/sync-documentation.md +76 -0
  247. package/template/workers/dev-team/knowledge-curator/skills/update-patterns.md +63 -0
  248. package/template/workers/dev-team/knowledge-curator/src/index.ts +53 -0
  249. package/template/workers/dev-team/knowledge-curator/src/mcp-server.ts +92 -0
  250. package/template/workers/dev-team/knowledge-curator/tsconfig.json +19 -0
  251. package/template/workers/dev-team/knowledge-curator/worker.yaml +80 -0
  252. package/template/workers/dev-team/motion-designer/package.json +22 -0
  253. package/template/workers/dev-team/motion-designer/skills/add-animation.md +25 -0
  254. package/template/workers/dev-team/motion-designer/skills/generate-image.md +36 -0
  255. package/template/workers/dev-team/motion-designer/src/index.ts +63 -0
  256. package/template/workers/dev-team/motion-designer/src/mcp-server.ts +79 -0
  257. package/template/workers/dev-team/motion-designer/tsconfig.json +18 -0
  258. package/template/workers/dev-team/motion-designer/worker.yaml +84 -0
  259. package/template/workers/dev-team/product-planner/queue.json +4 -0
  260. package/template/workers/dev-team/product-planner/worker.yaml +220 -0
  261. package/template/workers/dev-team/project-manager/package-lock.json +1252 -0
  262. package/template/workers/dev-team/project-manager/package.json +27 -0
  263. package/template/workers/dev-team/project-manager/skills/create-prd.md +66 -0
  264. package/template/workers/dev-team/project-manager/skills/next-issue.md +51 -0
  265. package/template/workers/dev-team/project-manager/skills/project-status.md +59 -0
  266. package/template/workers/dev-team/project-manager/skills/update-learnings.md +65 -0
  267. package/template/workers/dev-team/project-manager/src/index.ts +54 -0
  268. package/template/workers/dev-team/project-manager/src/mcp-server.ts +207 -0
  269. package/template/workers/dev-team/project-manager/src/skills/create-prd.ts +86 -0
  270. package/template/workers/dev-team/project-manager/src/skills/next-issue.ts +137 -0
  271. package/template/workers/dev-team/project-manager/src/skills/project-status.ts +131 -0
  272. package/template/workers/dev-team/project-manager/src/skills/update-learnings.ts +94 -0
  273. package/template/workers/dev-team/project-manager/tsconfig.json +19 -0
  274. package/template/workers/dev-team/project-manager/worker.yaml +96 -0
  275. package/template/workers/dev-team/qa-tester/package.json +24 -0
  276. package/template/workers/dev-team/qa-tester/skills/create-demo-account.md +36 -0
  277. package/template/workers/dev-team/qa-tester/skills/run-tests.md +36 -0
  278. package/template/workers/dev-team/qa-tester/skills/write-test.md +27 -0
  279. package/template/workers/dev-team/qa-tester/src/index.ts +61 -0
  280. package/template/workers/dev-team/qa-tester/src/mcp-server.ts +88 -0
  281. package/template/workers/dev-team/qa-tester/tsconfig.json +18 -0
  282. package/template/workers/dev-team/qa-tester/worker.yaml +116 -0
  283. package/template/workers/dev-team/task-executor/package-lock.json +1252 -0
  284. package/template/workers/dev-team/task-executor/package.json +27 -0
  285. package/template/workers/dev-team/task-executor/skills/analyze-issue.md +101 -0
  286. package/template/workers/dev-team/task-executor/skills/execute.md +133 -0
  287. package/template/workers/dev-team/task-executor/skills/report-learnings.md +106 -0
  288. package/template/workers/dev-team/task-executor/skills/validate-completion.md +121 -0
  289. package/template/workers/dev-team/task-executor/src/index.ts +54 -0
  290. package/template/workers/dev-team/task-executor/src/mcp-server.ts +139 -0
  291. package/template/workers/dev-team/task-executor/src/skills/analyze-issue.ts +219 -0
  292. package/template/workers/dev-team/task-executor/src/skills/execute.ts +132 -0
  293. package/template/workers/dev-team/task-executor/src/skills/report-learnings.ts +119 -0
  294. package/template/workers/dev-team/task-executor/src/skills/validate-completion.ts +142 -0
  295. package/template/workers/dev-team/task-executor/tsconfig.json +19 -0
  296. package/template/workers/dev-team/task-executor/worker.yaml +110 -0
  297. package/template/workers/registry.yaml +171 -0
  298. package/template/workers/security-scanner/README.md +73 -0
  299. package/template/workers/security-scanner/skills/pre-deploy-check.md +205 -0
  300. package/template/workers/security-scanner/worker.yaml +26 -0
  301. package/template/workspace/checkpoints/.gitkeep +0 -0
  302. package/template/workspace/content-ideas/inbox.jsonl +0 -0
  303. package/template/workspace/drafts/.gitkeep +0 -0
  304. package/template/workspace/learnings/.gitkeep +3 -0
  305. package/template/workspace/orchestrator/.gitkeep +0 -0
  306. package/template/workspace/ralph-test/COMPLETE.md +18 -0
  307. package/template/workspace/ralph-test/hello.txt +2 -0
  308. package/template/workspace/reports/.gitkeep +0 -0
  309. package/template/workspace/scratch/.gitkeep +0 -0
  310. package/template/workspace/threads/.gitkeep +3 -0
@@ -0,0 +1,749 @@
1
+ /**
2
+ * Report generation utilities for content analysis workers
3
+ * Enhanced for US-015: Full markdown report generation
4
+ */
5
+
6
+ import * as fs from 'node:fs';
7
+ import * as path from 'node:path';
8
+ import type {
9
+ AnalysisResult,
10
+ Finding,
11
+ Recommendation,
12
+ ScoreCategory,
13
+ ReportMeta,
14
+ FullAnalysis,
15
+ } from './types.js';
16
+ import { getGrade, getScoreLabel, countBySeverity, sortBySeverity } from './scorer.js';
17
+ import type { Suggestion } from './recommendations.js';
18
+ import type { VariantSet } from './variants.js';
19
+ import type { StructureAnalysis } from './restructure.js';
20
+
21
+ // ============================================
22
+ // Report Configuration
23
+ // ============================================
24
+
25
+ export interface ReportConfig {
26
+ includeScores: boolean;
27
+ includeFindings: boolean;
28
+ includeRecommendations: boolean;
29
+ includeVariants: boolean;
30
+ includeStructure: boolean;
31
+ maxRecommendations: number;
32
+ outputPath: string;
33
+ }
34
+
35
+ export const DEFAULT_REPORT_CONFIG: ReportConfig = {
36
+ includeScores: true,
37
+ includeFindings: true,
38
+ includeRecommendations: true,
39
+ includeVariants: false,
40
+ includeStructure: false,
41
+ maxRecommendations: 20,
42
+ outputPath: 'workspace/reports/content',
43
+ };
44
+
45
+ /**
46
+ * Generate markdown report from analysis result
47
+ */
48
+ export function generateMarkdownReport(result: AnalysisResult): string {
49
+ const lines: string[] = [];
50
+
51
+ // Header
52
+ lines.push(`# Content Analysis Report`);
53
+ lines.push('');
54
+ lines.push(`**Page:** ${result.pageUrl}`);
55
+ lines.push(`**Analyzed:** ${result.analyzedAt}`);
56
+ lines.push(`**Worker:** ${result.workerId}`);
57
+ lines.push('');
58
+
59
+ // Overall Score
60
+ lines.push('## Overall Score');
61
+ lines.push('');
62
+ lines.push(`**${result.overallScore}/100** (${getGrade(result.overallScore)}) - ${getScoreLabel(result.overallScore)}`);
63
+ lines.push('');
64
+
65
+ // Score breakdown
66
+ lines.push('### Score Breakdown');
67
+ lines.push('');
68
+ lines.push('| Category | Score | Grade |');
69
+ lines.push('|----------|-------|-------|');
70
+ for (const cat of result.categories) {
71
+ const pct = Math.round((cat.score / cat.maxScore) * 100);
72
+ lines.push(`| ${cat.name} | ${cat.score}/${cat.maxScore} (${pct}%) | ${getGrade(pct)} |`);
73
+ }
74
+ lines.push('');
75
+
76
+ // Findings summary
77
+ const counts = countBySeverity(result.findings);
78
+ lines.push('## Findings Summary');
79
+ lines.push('');
80
+ lines.push(`- **Critical:** ${counts.critical}`);
81
+ lines.push(`- **Warnings:** ${counts.warning}`);
82
+ lines.push(`- **Info:** ${counts.info}`);
83
+ lines.push(`- **Passed:** ${counts.pass}`);
84
+ lines.push('');
85
+
86
+ // Detailed findings
87
+ if (result.findings.length > 0) {
88
+ lines.push('## Detailed Findings');
89
+ lines.push('');
90
+
91
+ const sorted = sortBySeverity(result.findings);
92
+ for (const finding of sorted) {
93
+ const icon = getSeverityIcon(finding.severity);
94
+ lines.push(`### ${icon} ${finding.category}`);
95
+ lines.push('');
96
+ lines.push(finding.message);
97
+ if (finding.location) {
98
+ lines.push(`- **Location:** ${finding.location}`);
99
+ }
100
+ if (finding.evidence) {
101
+ lines.push(`- **Evidence:** "${finding.evidence}"`);
102
+ }
103
+ lines.push('');
104
+ }
105
+ }
106
+
107
+ // Recommendations
108
+ if (result.recommendations.length > 0) {
109
+ lines.push('## Recommendations');
110
+ lines.push('');
111
+
112
+ const byPriority = groupByPriority(result.recommendations);
113
+
114
+ if (byPriority.high.length > 0) {
115
+ lines.push('### High Priority');
116
+ lines.push('');
117
+ for (const rec of byPriority.high) {
118
+ lines.push(formatRecommendation(rec));
119
+ }
120
+ }
121
+
122
+ if (byPriority.medium.length > 0) {
123
+ lines.push('### Medium Priority');
124
+ lines.push('');
125
+ for (const rec of byPriority.medium) {
126
+ lines.push(formatRecommendation(rec));
127
+ }
128
+ }
129
+
130
+ if (byPriority.low.length > 0) {
131
+ lines.push('### Low Priority');
132
+ lines.push('');
133
+ for (const rec of byPriority.low) {
134
+ lines.push(formatRecommendation(rec));
135
+ }
136
+ }
137
+ }
138
+
139
+ return lines.join('\n');
140
+ }
141
+
142
+ /**
143
+ * Get severity icon for markdown
144
+ */
145
+ function getSeverityIcon(severity: Finding['severity']): string {
146
+ switch (severity) {
147
+ case 'critical': return '[CRITICAL]';
148
+ case 'warning': return '[WARNING]';
149
+ case 'info': return '[INFO]';
150
+ case 'pass': return '[PASS]';
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Format recommendation for markdown
156
+ */
157
+ function formatRecommendation(rec: Recommendation): string {
158
+ return [
159
+ `**${rec.category}**`,
160
+ `- Current: ${rec.current}`,
161
+ `- Suggested: ${rec.suggested}`,
162
+ `- Rationale: ${rec.rationale}`,
163
+ ''
164
+ ].join('\n');
165
+ }
166
+
167
+ /**
168
+ * Group recommendations by priority
169
+ */
170
+ function groupByPriority(recs: Recommendation[]): Record<Recommendation['priority'], Recommendation[]> {
171
+ return {
172
+ high: recs.filter(r => r.priority === 'high'),
173
+ medium: recs.filter(r => r.priority === 'medium'),
174
+ low: recs.filter(r => r.priority === 'low')
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Generate report filename
180
+ */
181
+ export function generateReportFilename(
182
+ workerId: string,
183
+ pageSlug: string
184
+ ): string {
185
+ const date = new Date().toISOString().split('T')[0];
186
+ return `${date}-${workerId}-${pageSlug}.md`;
187
+ }
188
+
189
+ /**
190
+ * Create report metadata
191
+ */
192
+ export function createReportMeta(
193
+ workerId: string,
194
+ pageUrl: string,
195
+ version: string
196
+ ): ReportMeta {
197
+ return {
198
+ generatedAt: new Date().toISOString(),
199
+ workerId,
200
+ pageAnalyzed: pageUrl,
201
+ version
202
+ };
203
+ }
204
+
205
+ /**
206
+ * Format date for reports
207
+ */
208
+ export function formatReportDate(date: Date = new Date()): string {
209
+ return date.toISOString().replace('T', ' ').split('.')[0] + ' UTC';
210
+ }
211
+
212
+ // ============================================
213
+ // Full Analysis Reports (US-015)
214
+ // ============================================
215
+
216
+ /**
217
+ * Generate full analysis report from FullAnalysis
218
+ */
219
+ export function generateFullReport(
220
+ analysis: FullAnalysis,
221
+ config: Partial<ReportConfig> = {}
222
+ ): string {
223
+ const cfg = { ...DEFAULT_REPORT_CONFIG, ...config };
224
+ const lines: string[] = [];
225
+
226
+ // Header
227
+ lines.push(`# Content Analysis Report`);
228
+ lines.push('');
229
+ lines.push(`**Page:** ${analysis.page}`);
230
+ lines.push(`**Analyzed:** ${formatReportDate(new Date(analysis.timestamp))}`);
231
+ lines.push('');
232
+
233
+ // Overall Health Score
234
+ lines.push('## Overall Health Score');
235
+ lines.push('');
236
+ lines.push(`**${analysis.overallHealth}/100** (${getGrade(analysis.overallHealth)}) - ${getScoreLabel(analysis.overallHealth)}`);
237
+ lines.push('');
238
+
239
+ // Score Breakdown
240
+ if (cfg.includeScores) {
241
+ lines.push('### Score Breakdown');
242
+ lines.push('');
243
+ lines.push('| Dimension | Score | Grade |');
244
+ lines.push('|-----------|-------|-------|');
245
+
246
+ if (analysis.brand) {
247
+ lines.push(`| Brand Voice | ${analysis.brand.overallScore}/100 | ${getGrade(analysis.brand.overallScore)} |`);
248
+ }
249
+ if (analysis.conversion) {
250
+ lines.push(`| Conversion | ${analysis.conversion.overallScore}/100 | ${getGrade(analysis.conversion.overallScore)} |`);
251
+ }
252
+ if (analysis.accuracy) {
253
+ lines.push(`| Accuracy | ${analysis.accuracy.overallScore}/100 | ${getGrade(analysis.accuracy.overallScore)} |`);
254
+ }
255
+ if (analysis.compliance) {
256
+ lines.push(`| Compliance | ${analysis.compliance.overallScore}/100 | ${getGrade(analysis.compliance.overallScore)} |`);
257
+ }
258
+ lines.push('');
259
+ }
260
+
261
+ // Findings Summary
262
+ if (cfg.includeFindings) {
263
+ const allFindings: Finding[] = [
264
+ ...(analysis.brand?.findings ?? []),
265
+ ...(analysis.conversion?.findings ?? []),
266
+ ...(analysis.accuracy?.findings ?? []),
267
+ ...(analysis.compliance?.findings ?? []),
268
+ ];
269
+
270
+ const counts = countBySeverity(allFindings);
271
+ lines.push('## Findings Summary');
272
+ lines.push('');
273
+ lines.push(`- **Critical:** ${counts.critical}`);
274
+ lines.push(`- **Warnings:** ${counts.warning}`);
275
+ lines.push(`- **Info:** ${counts.info}`);
276
+ lines.push(`- **Passed:** ${counts.pass}`);
277
+ lines.push('');
278
+
279
+ // Detailed findings by dimension
280
+ if (allFindings.length > 0) {
281
+ lines.push('### Detailed Findings');
282
+ lines.push('');
283
+
284
+ if (analysis.brand && analysis.brand.findings.length > 0) {
285
+ lines.push('#### Brand Voice');
286
+ for (const finding of sortBySeverity(analysis.brand.findings)) {
287
+ lines.push(formatFindingLine(finding));
288
+ }
289
+ lines.push('');
290
+ }
291
+
292
+ if (analysis.conversion && analysis.conversion.findings.length > 0) {
293
+ lines.push('#### Conversion');
294
+ for (const finding of sortBySeverity(analysis.conversion.findings)) {
295
+ lines.push(formatFindingLine(finding));
296
+ }
297
+ lines.push('');
298
+ }
299
+
300
+ if (analysis.accuracy && analysis.accuracy.findings.length > 0) {
301
+ lines.push('#### Accuracy');
302
+ for (const finding of sortBySeverity(analysis.accuracy.findings)) {
303
+ lines.push(formatFindingLine(finding));
304
+ }
305
+ lines.push('');
306
+ }
307
+
308
+ if (analysis.compliance && analysis.compliance.findings.length > 0) {
309
+ lines.push('#### Compliance');
310
+ for (const finding of sortBySeverity(analysis.compliance.findings)) {
311
+ lines.push(formatFindingLine(finding));
312
+ }
313
+ lines.push('');
314
+ }
315
+ }
316
+ }
317
+
318
+ // Top Priorities / Recommendations
319
+ if (cfg.includeRecommendations && analysis.topPriorities.length > 0) {
320
+ lines.push('## Top Priorities');
321
+ lines.push('');
322
+
323
+ const priorities = analysis.topPriorities.slice(0, cfg.maxRecommendations);
324
+ const byPriority = groupByPriority(priorities);
325
+
326
+ if (byPriority.high.length > 0) {
327
+ lines.push('### High Priority');
328
+ lines.push('');
329
+ for (const rec of byPriority.high) {
330
+ lines.push(formatRecommendation(rec));
331
+ }
332
+ }
333
+
334
+ if (byPriority.medium.length > 0) {
335
+ lines.push('### Medium Priority');
336
+ lines.push('');
337
+ for (const rec of byPriority.medium) {
338
+ lines.push(formatRecommendation(rec));
339
+ }
340
+ }
341
+
342
+ if (byPriority.low.length > 0) {
343
+ lines.push('### Low Priority');
344
+ lines.push('');
345
+ for (const rec of byPriority.low) {
346
+ lines.push(formatRecommendation(rec));
347
+ }
348
+ }
349
+ }
350
+
351
+ return lines.join('\n');
352
+ }
353
+
354
+ /**
355
+ * Format a single finding as a line
356
+ */
357
+ function formatFindingLine(finding: Finding): string {
358
+ const icon = getSeverityIcon(finding.severity);
359
+ let line = `- ${icon} **${finding.category}**: ${finding.message}`;
360
+ if (finding.evidence) {
361
+ line += ` ("${truncateText(finding.evidence, 60)}")`;
362
+ }
363
+ return line;
364
+ }
365
+
366
+ /**
367
+ * Truncate text with ellipsis
368
+ */
369
+ function truncateText(text: string, maxLen: number): string {
370
+ if (text.length <= maxLen) return text;
371
+ return text.slice(0, maxLen - 3) + '...';
372
+ }
373
+
374
+ /**
375
+ * Generate executive summary for multiple pages (1 page max)
376
+ */
377
+ export function generateExecutiveSummary(analyses: FullAnalysis[]): string {
378
+ const lines: string[] = [];
379
+
380
+ lines.push('# Executive Summary: Content Health');
381
+ lines.push('');
382
+ lines.push(`**Report Date:** ${formatReportDate()}`);
383
+ lines.push(`**Pages Analyzed:** ${analyses.length}`);
384
+ lines.push('');
385
+
386
+ // Overall statistics
387
+ const avgHealth = Math.round(
388
+ analyses.reduce((sum, a) => sum + a.overallHealth, 0) / analyses.length
389
+ );
390
+
391
+ // Count total issues
392
+ let totalCritical = 0;
393
+ let totalWarnings = 0;
394
+ for (const analysis of analyses) {
395
+ const allFindings = [
396
+ ...(analysis.brand?.findings ?? []),
397
+ ...(analysis.conversion?.findings ?? []),
398
+ ...(analysis.accuracy?.findings ?? []),
399
+ ...(analysis.compliance?.findings ?? []),
400
+ ];
401
+ for (const f of allFindings) {
402
+ if (f.severity === 'critical') totalCritical++;
403
+ if (f.severity === 'warning') totalWarnings++;
404
+ }
405
+ }
406
+
407
+ lines.push('## Overall Health');
408
+ lines.push('');
409
+ lines.push(`- **Average Score:** ${avgHealth}/100 (${getGrade(avgHealth)})`);
410
+ lines.push(`- **Critical Issues:** ${totalCritical}`);
411
+ lines.push(`- **Warnings:** ${totalWarnings}`);
412
+ lines.push('');
413
+
414
+ // Page summary table
415
+ lines.push('## Page Summary');
416
+ lines.push('');
417
+ lines.push('| Page | Health | Brand | Conversion | Accuracy | Compliance |');
418
+ lines.push('|------|--------|-------|------------|----------|------------|');
419
+
420
+ for (const analysis of analyses) {
421
+ const row = [
422
+ analysis.page,
423
+ `${analysis.overallHealth}`,
424
+ analysis.brand ? `${analysis.brand.overallScore}` : '-',
425
+ analysis.conversion ? `${analysis.conversion.overallScore}` : '-',
426
+ analysis.accuracy ? `${analysis.accuracy.overallScore}` : '-',
427
+ analysis.compliance ? `${analysis.compliance.overallScore}` : '-',
428
+ ];
429
+ lines.push(`| ${row.join(' | ')} |`);
430
+ }
431
+ lines.push('');
432
+
433
+ // Top 5 priorities across all pages
434
+ lines.push('## Top Priorities');
435
+ lines.push('');
436
+
437
+ const allPriorities: Array<Recommendation & { page: string }> = [];
438
+ for (const analysis of analyses) {
439
+ for (const rec of analysis.topPriorities) {
440
+ allPriorities.push({ ...rec, page: analysis.page });
441
+ }
442
+ }
443
+
444
+ // Sort by priority and take top 5
445
+ const priorityOrder: Record<string, number> = { high: 0, medium: 1, low: 2 };
446
+ allPriorities.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
447
+
448
+ for (const rec of allPriorities.slice(0, 5)) {
449
+ lines.push(`1. **[${rec.page}]** ${rec.category}: ${rec.suggested}`);
450
+ }
451
+ lines.push('');
452
+
453
+ return lines.join('\n');
454
+ }
455
+
456
+ /**
457
+ * Generate detailed report for a single page
458
+ */
459
+ export function generatePageReport(
460
+ analysis: FullAnalysis,
461
+ suggestions?: Suggestion[],
462
+ variants?: VariantSet[],
463
+ structure?: StructureAnalysis
464
+ ): string {
465
+ const lines: string[] = [];
466
+
467
+ // Base report
468
+ lines.push(generateFullReport(analysis, {
469
+ includeVariants: !!variants,
470
+ includeStructure: !!structure,
471
+ }));
472
+
473
+ // Add suggestions section
474
+ if (suggestions && suggestions.length > 0) {
475
+ lines.push('');
476
+ lines.push('## Improvement Suggestions');
477
+ lines.push('');
478
+
479
+ for (const s of suggestions.slice(0, 10)) {
480
+ const effortBadge = s.effort === 'quick' ? 'Quick' : s.effort === 'moderate' ? 'Moderate' : 'Major';
481
+ lines.push(`### ${s.type.toUpperCase()}: ${s.sectionId ?? 'Page-level'}`);
482
+ lines.push(`**Impact:** ${s.impact} | **Effort:** ${effortBadge} | **Source:** ${s.source}`);
483
+ lines.push('');
484
+ lines.push('**Current:**');
485
+ lines.push(`> ${s.original}`);
486
+ lines.push('');
487
+ lines.push('**Suggested:**');
488
+ lines.push(`> ${s.suggested}`);
489
+ lines.push('');
490
+ lines.push(`_${s.rationale}_`);
491
+ lines.push('');
492
+ }
493
+ }
494
+
495
+ // Add variants section
496
+ if (variants && variants.length > 0) {
497
+ lines.push('');
498
+ lines.push('## A/B Copy Variants');
499
+ lines.push('');
500
+
501
+ for (const vs of variants.slice(0, 5)) {
502
+ lines.push(`### ${vs.sectionId ?? 'Content'}`);
503
+ lines.push('');
504
+ lines.push('**Original:**');
505
+ lines.push(`> ${vs.original}`);
506
+ lines.push('');
507
+
508
+ for (const v of vs.variants) {
509
+ lines.push(`**${formatApproachLabel(v.approach)}:**`);
510
+ lines.push(`> ${v.text}`);
511
+ lines.push('');
512
+ }
513
+ }
514
+ }
515
+
516
+ // Add structure section
517
+ if (structure) {
518
+ lines.push('');
519
+ lines.push('## Structure Analysis');
520
+ lines.push('');
521
+ lines.push(`**Template:** ${structure.templateUsed}`);
522
+ lines.push(`**Score:** ${structure.structureScore}/100`);
523
+ lines.push('');
524
+
525
+ if (structure.missingSections.length > 0) {
526
+ lines.push('**Missing Sections:**');
527
+ for (const s of structure.missingSections) {
528
+ lines.push(`- ${s}`);
529
+ }
530
+ lines.push('');
531
+ }
532
+
533
+ if (structure.orderIssues.length > 0) {
534
+ lines.push('**Order Issues:**');
535
+ for (const i of structure.orderIssues) {
536
+ lines.push(`- ${i}`);
537
+ }
538
+ lines.push('');
539
+ }
540
+ }
541
+
542
+ return lines.join('\n');
543
+ }
544
+
545
+ /**
546
+ * Format approach label
547
+ */
548
+ function formatApproachLabel(approach: string): string {
549
+ const labels: Record<string, string> = {
550
+ 'emotional': 'Emotional',
551
+ 'logical': 'Logical',
552
+ 'urgent': 'Urgency',
553
+ 'social-proof': 'Social Proof',
554
+ 'benefit-focused': 'Benefit-Focused',
555
+ };
556
+ return labels[approach] ?? approach;
557
+ }
558
+
559
+ /**
560
+ * Generate comparison report (before/after)
561
+ */
562
+ export function generateComparisonReport(
563
+ before: FullAnalysis,
564
+ after: FullAnalysis
565
+ ): string {
566
+ const lines: string[] = [];
567
+
568
+ lines.push('# Content Analysis Comparison');
569
+ lines.push('');
570
+ lines.push(`**Page:** ${before.page}`);
571
+ lines.push(`**Before:** ${formatReportDate(new Date(before.timestamp))}`);
572
+ lines.push(`**After:** ${formatReportDate(new Date(after.timestamp))}`);
573
+ lines.push('');
574
+
575
+ // Health delta
576
+ const healthDelta = after.overallHealth - before.overallHealth;
577
+ const deltaIcon = healthDelta > 0 ? '+' : healthDelta < 0 ? '' : '';
578
+ lines.push('## Overall Health');
579
+ lines.push('');
580
+ lines.push(`| Metric | Before | After | Change |`);
581
+ lines.push(`|--------|--------|-------|--------|`);
582
+ lines.push(`| Health Score | ${before.overallHealth} | ${after.overallHealth} | ${deltaIcon}${healthDelta} |`);
583
+
584
+ // Dimension deltas
585
+ if (before.brand && after.brand) {
586
+ const delta = after.brand.overallScore - before.brand.overallScore;
587
+ lines.push(`| Brand | ${before.brand.overallScore} | ${after.brand.overallScore} | ${delta >= 0 ? '+' : ''}${delta} |`);
588
+ }
589
+ if (before.conversion && after.conversion) {
590
+ const delta = after.conversion.overallScore - before.conversion.overallScore;
591
+ lines.push(`| Conversion | ${before.conversion.overallScore} | ${after.conversion.overallScore} | ${delta >= 0 ? '+' : ''}${delta} |`);
592
+ }
593
+ if (before.accuracy && after.accuracy) {
594
+ const delta = after.accuracy.overallScore - before.accuracy.overallScore;
595
+ lines.push(`| Accuracy | ${before.accuracy.overallScore} | ${after.accuracy.overallScore} | ${delta >= 0 ? '+' : ''}${delta} |`);
596
+ }
597
+ if (before.compliance && after.compliance) {
598
+ const delta = after.compliance.overallScore - before.compliance.overallScore;
599
+ lines.push(`| Compliance | ${before.compliance.overallScore} | ${after.compliance.overallScore} | ${delta >= 0 ? '+' : ''}${delta} |`);
600
+ }
601
+ lines.push('');
602
+
603
+ // Findings comparison
604
+ const beforeFindings = countAllFindings(before);
605
+ const afterFindings = countAllFindings(after);
606
+
607
+ lines.push('## Findings Comparison');
608
+ lines.push('');
609
+ lines.push('| Severity | Before | After | Change |');
610
+ lines.push('|----------|--------|-------|--------|');
611
+ lines.push(`| Critical | ${beforeFindings.critical} | ${afterFindings.critical} | ${formatDelta(afterFindings.critical - beforeFindings.critical, true)} |`);
612
+ lines.push(`| Warning | ${beforeFindings.warning} | ${afterFindings.warning} | ${formatDelta(afterFindings.warning - beforeFindings.warning, true)} |`);
613
+ lines.push('');
614
+
615
+ // Summary
616
+ lines.push('## Summary');
617
+ lines.push('');
618
+ if (healthDelta > 0) {
619
+ lines.push(`Content health improved by **${healthDelta} points**.`);
620
+ } else if (healthDelta < 0) {
621
+ lines.push(`Content health decreased by **${Math.abs(healthDelta)} points**.`);
622
+ } else {
623
+ lines.push('Content health remained unchanged.');
624
+ }
625
+
626
+ const criticalDelta = afterFindings.critical - beforeFindings.critical;
627
+ if (criticalDelta < 0) {
628
+ lines.push(`Resolved **${Math.abs(criticalDelta)} critical** issues.`);
629
+ } else if (criticalDelta > 0) {
630
+ lines.push(`**${criticalDelta} new critical** issues detected.`);
631
+ }
632
+ lines.push('');
633
+
634
+ return lines.join('\n');
635
+ }
636
+
637
+ /**
638
+ * Count all findings across dimensions
639
+ */
640
+ function countAllFindings(analysis: FullAnalysis): Record<Finding['severity'], number> {
641
+ const allFindings: Finding[] = [
642
+ ...(analysis.brand?.findings ?? []),
643
+ ...(analysis.conversion?.findings ?? []),
644
+ ...(analysis.accuracy?.findings ?? []),
645
+ ...(analysis.compliance?.findings ?? []),
646
+ ];
647
+ return countBySeverity(allFindings);
648
+ }
649
+
650
+ /**
651
+ * Format delta with appropriate sign (inverse for issues where less is better)
652
+ */
653
+ function formatDelta(delta: number, inverse = false): string {
654
+ if (delta === 0) return '0';
655
+ if (inverse) {
656
+ // For issues, negative delta is good (fewer issues)
657
+ return delta < 0 ? `${delta} (improved)` : `+${delta} (worse)`;
658
+ }
659
+ return delta > 0 ? `+${delta}` : `${delta}`;
660
+ }
661
+
662
+ // ============================================
663
+ // File Output Functions
664
+ // ============================================
665
+
666
+ /**
667
+ * Write report to file
668
+ */
669
+ export function writeReport(
670
+ content: string,
671
+ filename: string,
672
+ outputPath: string
673
+ ): string {
674
+ // Ensure output directory exists
675
+ const fullPath = path.resolve(outputPath);
676
+ if (!fs.existsSync(fullPath)) {
677
+ fs.mkdirSync(fullPath, { recursive: true });
678
+ }
679
+
680
+ const filePath = path.join(fullPath, filename);
681
+ fs.writeFileSync(filePath, content, 'utf-8');
682
+
683
+ return filePath;
684
+ }
685
+
686
+ /**
687
+ * Generate report file path with standard naming
688
+ * Format: {YYYY-MM-DD}-{worker}-{page-slug}.md
689
+ */
690
+ export function generateReportPath(
691
+ worker: string,
692
+ page: string,
693
+ outputPath: string
694
+ ): string {
695
+ const date = new Date().toISOString().split('T')[0];
696
+ const pageSlug = page.replace(/[^a-z0-9]+/gi, '-').toLowerCase().replace(/^-|-$/g, '');
697
+ const filename = `${date}-${worker}-${pageSlug}.md`;
698
+
699
+ return path.join(outputPath, filename);
700
+ }
701
+
702
+ /**
703
+ * Save full analysis report to file
704
+ */
705
+ export function saveFullReport(
706
+ analysis: FullAnalysis,
707
+ worker: string,
708
+ outputPath: string = DEFAULT_REPORT_CONFIG.outputPath,
709
+ config?: Partial<ReportConfig>
710
+ ): string {
711
+ const content = generateFullReport(analysis, config);
712
+ const reportPath = generateReportPath(worker, analysis.page, outputPath);
713
+ const dir = path.dirname(reportPath);
714
+ const filename = path.basename(reportPath);
715
+
716
+ return writeReport(content, filename, dir);
717
+ }
718
+
719
+ /**
720
+ * Save executive summary to file
721
+ */
722
+ export function saveExecutiveSummary(
723
+ analyses: FullAnalysis[],
724
+ worker: string,
725
+ outputPath: string = DEFAULT_REPORT_CONFIG.outputPath
726
+ ): string {
727
+ const content = generateExecutiveSummary(analyses);
728
+ const date = new Date().toISOString().split('T')[0];
729
+ const filename = `${date}-${worker}-executive-summary.md`;
730
+
731
+ return writeReport(content, filename, outputPath);
732
+ }
733
+
734
+ /**
735
+ * Save comparison report to file
736
+ */
737
+ export function saveComparisonReport(
738
+ before: FullAnalysis,
739
+ after: FullAnalysis,
740
+ worker: string,
741
+ outputPath: string = DEFAULT_REPORT_CONFIG.outputPath
742
+ ): string {
743
+ const content = generateComparisonReport(before, after);
744
+ const date = new Date().toISOString().split('T')[0];
745
+ const pageSlug = before.page.replace(/[^a-z0-9]+/gi, '-').toLowerCase();
746
+ const filename = `${date}-${worker}-${pageSlug}-comparison.md`;
747
+
748
+ return writeReport(content, filename, outputPath);
749
+ }