claude-flow-novice 2.15.3 → 2.15.4

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 (461) hide show
  1. package/.claude/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
  2. package/.claude/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
  3. package/.claude/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
  4. package/.claude/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
  5. package/.claude/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
  6. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
  7. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
  8. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
  9. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
  10. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
  11. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
  12. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
  13. package/.claude/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
  14. package/.claude/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
  15. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
  16. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
  17. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
  18. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
  19. package/.claude/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
  20. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
  21. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
  22. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
  23. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
  24. package/.claude/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
  25. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
  26. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
  27. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
  28. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
  29. package/.claude/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
  30. package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
  31. package/.claude/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
  32. package/.claude/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
  33. package/.claude/commands/cfn-loop-cli.md +16 -2
  34. package/.claude/commands/switch-api.md +31 -10
  35. package/.claude/hooks/cfn-lint-sql-injection.sh +61 -0
  36. package/.claude/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
  37. package/.claude/hooks/cfn-pre-edit-security-warning.sh +40 -0
  38. package/.claude/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
  39. package/.claude/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
  40. package/.claude/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
  41. package/.claude/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
  42. package/.claude/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
  43. package/.claude/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
  44. package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
  45. package/.claude/skills/cfn-loop-orchestration/security_utils.sh +24 -0
  46. package/.claude/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
  47. package/.claude/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
  48. package/.claude/skills/cfn-redis-coordination/agent-log.sh +4 -0
  49. package/.claude/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
  50. package/.claude/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
  51. package/.claude/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
  52. package/.claude/skills/cfn-redis-coordination/get-context.sh +33 -0
  53. package/.claude/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
  54. package/.claude/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
  55. package/.claude/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
  56. package/.claude/skills/cfn-redis-coordination/redis-functions.sh +33 -0
  57. package/.claude/skills/cfn-redis-coordination/report-completion.sh +24 -31
  58. package/.claude/skills/cfn-redis-coordination/store-context.sh +4 -0
  59. package/.claude/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
  60. package/.claude/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
  61. package/.claude/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
  62. package/.claude/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
  63. package/.claude/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
  64. package/README.md +116 -475
  65. package/claude-assets/agents/cfn-dev-team/README.md +103 -0
  66. package/claude-assets/agents/cfn-dev-team/architecture/goal-planner.md +1 -1
  67. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +77 -15
  68. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +355 -6
  69. package/claude-assets/agents/cfn-dev-team/coordinators/consensus-builder.md +82 -1
  70. package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +82 -1
  71. package/claude-assets/agents/cfn-dev-team/coordinators/multi-sprint-coordinator.md +77 -15
  72. package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +99 -12
  73. package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +1 -1
  74. package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +97 -0
  75. package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +20 -1
  76. package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +97 -0
  77. package/claude-assets/agents/cfn-dev-team/developers/backend-developer.md +110 -13
  78. package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +106 -15
  79. package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +115 -11
  80. package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +94 -7
  81. package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +87 -9
  82. package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +85 -7
  83. package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +160 -28
  84. package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +101 -19
  85. package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +108 -14
  86. package/claude-assets/agents/cfn-dev-team/reviewers/{reviewer.md → code-reviewer.md} +95 -8
  87. package/claude-assets/agents/cfn-dev-team/reviewers/quality/code-quality-validator.md +107 -7
  88. package/claude-assets/agents/cfn-dev-team/reviewers/quality/perf-analyzer.md +98 -7
  89. package/claude-assets/agents/cfn-dev-team/reviewers/quality/performance-benchmarker.md +95 -7
  90. package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +136 -9
  91. package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +108 -1
  92. package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +107 -13
  93. package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +737 -0
  94. package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
  95. package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +828 -0
  96. package/claude-assets/agents/cfn-dev-team/testers/interaction-tester.md +106 -7
  97. package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +77 -0
  98. package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +684 -0
  99. package/claude-assets/agents/cfn-dev-team/testers/playwright-tester.md +110 -1
  100. package/claude-assets/agents/cfn-dev-team/testers/tester.md +94 -7
  101. package/claude-assets/agents/cfn-dev-team/utility/code-booster.md +1 -3
  102. package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +87 -13
  103. package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +103 -7
  104. package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -3
  105. package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +94 -7
  106. package/claude-assets/agents/docker-coordinators/cfn-docker-v3-coordinator.md +46 -0
  107. package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
  108. package/claude-assets/cfn-extras/skills/advanced-features/cfn-agent-swap/recommend-swap.sh +59 -59
  109. package/claude-assets/cfn-extras/skills/analytics/cfn-improvement-recommender/recommend-improvements.sh +91 -91
  110. package/claude-assets/cfn-extras/skills/analytics/cfn-pattern-extraction/extract-patterns.sh +79 -79
  111. package/claude-assets/cfn-extras/skills/analytics/cfn-retrospective-report/generate-report.sh +100 -100
  112. package/claude-assets/cfn-extras/skills/analytics/cfn-telemetry/start-telemetry.sh +110 -110
  113. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/add-bullet.sh +145 -145
  114. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/log-merge.sh +67 -67
  115. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/monitor-injection-performance.sh +137 -137
  116. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/optimize-injection-pipeline.sh +168 -168
  117. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/query-reflections.sh +35 -35
  118. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/store-reflection.sh +45 -45
  119. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/track-ab-test.sh +41 -41
  120. package/claude-assets/cfn-extras/skills/deprecated/cfn-ace-system/update-reflection.sh +41 -41
  121. package/claude-assets/cfn-extras/skills/deprecated/cfn-cli-setup/validate-cli-environment.sh +191 -191
  122. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/create-campaign.sh +231 -231
  123. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/get-campaign-performance.sh +190 -190
  124. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/pause-campaign.sh +142 -142
  125. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/set-budget.sh +181 -181
  126. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-ad-campaigns/operations/update-bid-strategy.sh +133 -133
  127. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/get-conversation-history.sh +121 -121
  128. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/qualify-lead.sh +156 -156
  129. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/schedule-demo.sh +181 -181
  130. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/send-message.sh +137 -137
  131. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-chatbot-conversations/operations/transfer-to-human.sh +179 -179
  132. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/create-campaign.sh +183 -183
  133. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/get-delivery-status.sh +139 -139
  134. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/opt-out.sh +150 -150
  135. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/schedule-campaign.sh +187 -187
  136. package/claude-assets/cfn-extras/skills/marketing/cfn-marketing-sms-campaigns/operations/send-sms.sh +181 -181
  137. package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/test-web-portal-skill.sh +50 -50
  138. package/claude-assets/cfn-extras/skills/ui-portal/cfn-web-portal/validate-deployment.sh +84 -84
  139. package/claude-assets/cfn-extras/skills/utility/cfn-environment-sanitization/sanitize-environment.sh +243 -243
  140. package/claude-assets/commands/cfn-loop-cli.md +16 -2
  141. package/claude-assets/commands/switch-api.md +31 -10
  142. package/claude-assets/hooks/cfn-lint-sql-injection.sh +61 -0
  143. package/claude-assets/hooks/cfn-post-edit-cfn-retrospective.sh +33 -2
  144. package/claude-assets/hooks/cfn-pre-edit-security-warning.sh +40 -0
  145. package/claude-assets/hooks/detect-hardcoded-credentials.sh +212 -0
  146. package/claude-assets/skills/SKILL_TEMPLATE.md +774 -0
  147. package/claude-assets/skills/agent-lifecycle/execute-lifecycle-hook.sh +84 -113
  148. package/claude-assets/skills/agent-lifecycle/simple-audit.sh +33 -6
  149. package/claude-assets/skills/agent-template-generator/SKILL.md +440 -0
  150. package/claude-assets/skills/agent-template-generator/generate-agent.sh +405 -0
  151. package/claude-assets/skills/agent-validation-linter/SKILL.md +589 -0
  152. package/claude-assets/skills/agent-validation-linter/lint-agents.sh +271 -0
  153. package/claude-assets/skills/bootstrap/bash-fundamentals.md +786 -0
  154. package/claude-assets/skills/bootstrap/database-connection.md +464 -0
  155. package/claude-assets/skills/bootstrap/error-handling.md +580 -0
  156. package/claude-assets/skills/bootstrap/file-operations.md +699 -0
  157. package/claude-assets/skills/bootstrap/skill-loader.md +616 -0
  158. package/claude-assets/skills/bootstrap/sqlite-params.sh +287 -0
  159. package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh +22 -24
  160. package/claude-assets/skills/cfn-automatic-memory-persistence/test-memory-persistence.sh +17 -16
  161. package/claude-assets/skills/cfn-deployment/SKILL.md +293 -0
  162. package/claude-assets/skills/cfn-deployment/execute.sh +21 -0
  163. package/claude-assets/skills/cfn-docker-agent-spawning/SKILL.md +28 -4
  164. package/claude-assets/skills/cfn-docker-agent-spawning/spawn-agent.sh +3 -1
  165. package/claude-assets/skills/cfn-docker-loop-orchestration/orchestrate.sh +224 -20
  166. package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +38 -0
  167. package/claude-assets/skills/cfn-error-batching-strategy/lib/core-functions.sh +47 -47
  168. package/claude-assets/skills/cfn-file-operations/SKILL.md +290 -0
  169. package/claude-assets/skills/cfn-file-operations/execute.sh +129 -0
  170. package/claude-assets/skills/cfn-file-operations/lib/atomic-write.sh +294 -0
  171. package/claude-assets/skills/cfn-file-operations/lib/lock.sh +361 -0
  172. package/claude-assets/skills/cfn-file-operations/test.sh +369 -0
  173. package/claude-assets/skills/cfn-log-operations/SKILL.md +308 -0
  174. package/claude-assets/skills/cfn-log-operations/execute.sh +420 -0
  175. package/claude-assets/skills/cfn-log-operations/lib/rotate.sh +406 -0
  176. package/claude-assets/skills/cfn-log-operations/lib/search.sh +448 -0
  177. package/claude-assets/skills/cfn-log-operations/test.sh +394 -0
  178. package/claude-assets/skills/cfn-loop-orchestration/helpers/gate-check.sh +550 -46
  179. package/claude-assets/skills/cfn-loop-orchestration/helpers/parse-test-results.sh +277 -0
  180. package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +184 -23
  181. package/claude-assets/skills/cfn-loop-orchestration/security_utils.sh +24 -0
  182. package/claude-assets/skills/cfn-loop-orchestration/test-iteration-context-injection.sh +366 -0
  183. package/claude-assets/skills/cfn-parameterized-queries/SKILL.md +339 -0
  184. package/claude-assets/skills/cfn-playbook/query-playbook.sh +19 -15
  185. package/claude-assets/skills/cfn-playbook/update-playbook.sh +25 -14
  186. package/claude-assets/skills/cfn-process-instrumentation/instrument-process.sh +44 -0
  187. package/claude-assets/skills/cfn-promotion/SKILL.md +305 -0
  188. package/claude-assets/skills/cfn-redis-coordination/CENTRALIZED_REDIS_WRAPPER.md +319 -0
  189. package/claude-assets/skills/cfn-redis-coordination/agent-log.sh +4 -0
  190. package/claude-assets/skills/cfn-redis-coordination/agent-log.sh.bak +124 -0
  191. package/claude-assets/skills/cfn-redis-coordination/agent-recovery.sh +2 -2
  192. package/claude-assets/skills/cfn-redis-coordination/collect-confidence-scores.sh +30 -0
  193. package/claude-assets/skills/cfn-redis-coordination/get-context.sh +33 -0
  194. package/claude-assets/skills/cfn-redis-coordination/get-success-criteria.sh +54 -0
  195. package/claude-assets/skills/cfn-redis-coordination/invoke-waiting-mode.sh +3 -0
  196. package/claude-assets/skills/cfn-redis-coordination/redis-cli-wrapper.sh +24 -3
  197. package/claude-assets/skills/cfn-redis-coordination/redis-functions.sh +33 -0
  198. package/claude-assets/skills/cfn-redis-coordination/report-completion.sh +24 -31
  199. package/claude-assets/skills/cfn-redis-coordination/store-context.sh +4 -0
  200. package/claude-assets/skills/cfn-redis-coordination/store-success-criteria.sh +85 -0
  201. package/claude-assets/skills/cfn-redis-coordination/update-all-scripts.sh +67 -0
  202. package/claude-assets/skills/cfn-skill-loader/SKILL.md +466 -0
  203. package/claude-assets/skills/cfn-skill-loader/execute.sh +344 -0
  204. package/claude-assets/skills/cfn-sqlite-memory/ttl-cleanup.sh +17 -25
  205. package/claude-assets/skills/cfn-task-audit/get-audit-data.sh +42 -21
  206. package/claude-assets/skills/cfn-task-audit/store-task-audit.sh +17 -10
  207. package/claude-assets/skills/cfn-test-runner/detect-regressions.sh +17 -14
  208. package/claude-assets/skills/cfn-test-runner/detect-regressions.sh.backup-1763392821 +55 -0
  209. package/claude-assets/skills/cfn-test-runner/store-benchmarks.sh +17 -19
  210. package/claude-assets/skills/cfn-transparency-middleware/test-e2e.sh +15 -0
  211. package/claude-assets/skills/cfn-transparency-middleware/tests/input-validation.sh +15 -0
  212. package/claude-assets/skills/cfn-utilities/SKILL.md +237 -0
  213. package/claude-assets/skills/cfn-utilities/execute.sh +32 -0
  214. package/claude-assets/skills/cfn-utilities/lib/errors.sh +56 -0
  215. package/claude-assets/skills/cfn-utilities/lib/file-ops.sh +164 -0
  216. package/claude-assets/skills/cfn-utilities/lib/logging.sh +77 -0
  217. package/claude-assets/skills/cfn-utilities/lib/retry.sh +127 -0
  218. package/claude-assets/skills/cfn-utilities/test.sh +317 -0
  219. package/claude-assets/skills/integration/agent-handoff.sh +62 -64
  220. package/claude-assets/skills/json-validation/SKILL.md +431 -0
  221. package/claude-assets/skills/json-validation/test-validate-success-criteria.sh +421 -0
  222. package/claude-assets/skills/json-validation/validate-success-criteria.sh +197 -0
  223. package/claude-assets/skills/redis-coordination/validate-parameters.sh +34 -0
  224. package/claude-assets/skills/workflow-codification/DEPLOY_QUICK_REFERENCE.md +106 -0
  225. package/claude-assets/skills/workflow-codification/PROPAGATE_UPDATE_QUICK_REFERENCE.md +366 -0
  226. package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh +481 -0
  227. package/claude-assets/skills/workflow-codification/deploy-approved-skill.sh.backup-1763392820 +512 -0
  228. package/claude-assets/skills/workflow-codification/lib/security-utils.sh +204 -0
  229. package/claude-assets/skills/workflow-codification/propagate-skill-update.sh +648 -0
  230. package/claude-assets/skills/workflow-codification/propagate-skill-update.sh.backup-1763392820 +664 -0
  231. package/claude-assets/skills/workflow-codification/test-integration.sh +15 -0
  232. package/claude-assets/skills/workflow-codification/test-metadata-update.sh +350 -0
  233. package/claude-assets/skills/workflow-codification/track-cost-savings.sh +55 -14
  234. package/claude-assets/skills/workflow-codification/track-cost-savings.sh.backup-1763392821 +445 -0
  235. package/claude-assets/skills/workflow-codification/track-edge-case.sh +27 -60
  236. package/claude-assets/skills/workflow-codification/workflow-codification.db +0 -0
  237. package/dist/ace/ace-curator.js +10 -2
  238. package/dist/ace/ace-curator.js.map +1 -1
  239. package/dist/ace/ace-generator.js +4 -0
  240. package/dist/ace/ace-generator.js.map +1 -1
  241. package/dist/ace/ace-reflector.js +1 -1
  242. package/dist/ace/ace-reflector.js.map +1 -1
  243. package/dist/ace/context-injection.js +24 -2
  244. package/dist/ace/context-injection.js.map +1 -1
  245. package/dist/agents/agent-loader.js +146 -165
  246. package/dist/agents/agent-loader.js.map +1 -1
  247. package/dist/agents/task-agent-integration.js +1 -1
  248. package/dist/agents/task-agent-integration.js.map +1 -1
  249. package/dist/api/health-endpoints.js +390 -0
  250. package/dist/api/health-endpoints.js.map +1 -0
  251. package/dist/cli/agent-executor.js +4 -1
  252. package/dist/cli/agent-executor.js.map +1 -1
  253. package/dist/cli/agent-prompt-builder.js +89 -1
  254. package/dist/cli/agent-prompt-builder.js.map +1 -1
  255. package/dist/cli/agent-spawn.js +130 -37
  256. package/dist/cli/agent-spawn.js.map +1 -1
  257. package/dist/cli/skill-cache-validator.js +412 -0
  258. package/dist/cli/skill-cache-validator.js.map +1 -0
  259. package/dist/cli/skill-cli.js +991 -0
  260. package/dist/cli/skill-cli.js.map +1 -0
  261. package/dist/cli/skill-execution-logger.js +284 -0
  262. package/dist/cli/skill-execution-logger.js.map +1 -0
  263. package/dist/cli/skill-loader.js +457 -0
  264. package/dist/cli/skill-loader.js.map +1 -0
  265. package/dist/coordination/event-bus.js +2 -2
  266. package/dist/coordination/event-bus.js.map +1 -1
  267. package/dist/coordination/fleet-manager.js +1 -1
  268. package/dist/coordination/fleet-manager.js.map +1 -1
  269. package/dist/coordination/index.js +23 -9
  270. package/dist/coordination/index.js.map +1 -1
  271. package/dist/coordination/types/fleet-manager.types.js.map +1 -1
  272. package/dist/db/migration-manager.js +483 -0
  273. package/dist/db/migration-manager.js.map +1 -0
  274. package/dist/db/skills-query.js +535 -0
  275. package/dist/db/skills-query.js.map +1 -0
  276. package/dist/integration/DatabaseHandoff.js +1 -1
  277. package/dist/integration/DatabaseHandoff.js.map +1 -1
  278. package/dist/jobs/edge-case-analyzer.js +367 -0
  279. package/dist/jobs/edge-case-analyzer.js.map +1 -0
  280. package/dist/jobs/promotion-sla-enforcer.js +288 -0
  281. package/dist/jobs/promotion-sla-enforcer.js.map +1 -0
  282. package/dist/lib/agent-output-parser.js.map +1 -1
  283. package/dist/lib/agent-output-validator.js.map +1 -1
  284. package/dist/lib/agent-workspace.js +281 -0
  285. package/dist/lib/agent-workspace.js.map +1 -0
  286. package/dist/lib/atomic-file-writer.js +377 -0
  287. package/dist/lib/atomic-file-writer.js.map +1 -0
  288. package/dist/lib/backup-manager.js +779 -0
  289. package/dist/lib/backup-manager.js.map +1 -0
  290. package/dist/lib/checkpoint-manager.js +837 -0
  291. package/dist/lib/checkpoint-manager.js.map +1 -0
  292. package/dist/lib/circuit-breaker.js +340 -0
  293. package/dist/lib/circuit-breaker.js.map +1 -0
  294. package/dist/lib/completion-signal-handler.js +243 -0
  295. package/dist/lib/completion-signal-handler.js.map +1 -0
  296. package/dist/lib/config-manager.js +312 -0
  297. package/dist/lib/config-manager.js.map +1 -0
  298. package/dist/lib/config-migrator.js +386 -0
  299. package/dist/lib/config-migrator.js.map +1 -0
  300. package/dist/lib/config-validator.js.map +1 -1
  301. package/dist/lib/correlation-cache.js +311 -0
  302. package/dist/lib/correlation-cache.js.map +1 -0
  303. package/dist/lib/correlation.js +263 -0
  304. package/dist/lib/correlation.js.map +1 -0
  305. package/dist/lib/database-service/connection-pool-manager.js +520 -0
  306. package/dist/lib/database-service/connection-pool-manager.js.map +1 -0
  307. package/dist/lib/database-service/correlation.js +329 -0
  308. package/dist/lib/database-service/correlation.js.map +1 -0
  309. package/dist/lib/database-service/errors.js +120 -0
  310. package/dist/lib/database-service/errors.js.map +1 -0
  311. package/dist/lib/database-service/index.js +168 -0
  312. package/dist/lib/database-service/index.js.map +1 -0
  313. package/dist/lib/database-service/postgres-adapter.js +526 -0
  314. package/dist/lib/database-service/postgres-adapter.js.map +1 -0
  315. package/dist/lib/database-service/redis-adapter.js +360 -0
  316. package/dist/lib/database-service/redis-adapter.js.map +1 -0
  317. package/dist/lib/database-service/sqlite-adapter.js +544 -0
  318. package/dist/lib/database-service/sqlite-adapter.js.map +1 -0
  319. package/dist/lib/database-service/transaction-manager.js +773 -0
  320. package/dist/lib/database-service/transaction-manager.js.map +1 -0
  321. package/dist/lib/database-service/types.js +23 -0
  322. package/dist/lib/database-service/types.js.map +1 -0
  323. package/dist/lib/deadlock-resolver.js +292 -0
  324. package/dist/lib/deadlock-resolver.js.map +1 -0
  325. package/dist/lib/distributed-lock.js +451 -0
  326. package/dist/lib/distributed-lock.js.map +1 -0
  327. package/dist/lib/edge-case-deduplicator.js +227 -0
  328. package/dist/lib/edge-case-deduplicator.js.map +1 -0
  329. package/dist/lib/encryption-manager.js +322 -0
  330. package/dist/lib/encryption-manager.js.map +1 -0
  331. package/dist/lib/error-aggregator.js +234 -0
  332. package/dist/lib/error-aggregator.js.map +1 -0
  333. package/dist/lib/errors.js +287 -0
  334. package/dist/lib/errors.js.map +1 -0
  335. package/dist/lib/file-lock-manager.js +578 -0
  336. package/dist/lib/file-lock-manager.js.map +1 -0
  337. package/dist/lib/file-operations.js +367 -0
  338. package/dist/lib/file-operations.js.map +1 -0
  339. package/dist/lib/idempotent-write.js +237 -0
  340. package/dist/lib/idempotent-write.js.map +1 -0
  341. package/dist/lib/integration-schema-validator.js +522 -0
  342. package/dist/lib/integration-schema-validator.js.map +1 -0
  343. package/dist/lib/lock-health-monitor.js +298 -0
  344. package/dist/lib/lock-health-monitor.js.map +1 -0
  345. package/dist/lib/log-shipper.js +422 -0
  346. package/dist/lib/log-shipper.js.map +1 -0
  347. package/dist/lib/logging.js +146 -0
  348. package/dist/lib/logging.js.map +1 -0
  349. package/dist/lib/message-deduplicator.js +439 -0
  350. package/dist/lib/message-deduplicator.js.map +1 -0
  351. package/dist/lib/multi-system-query.js +604 -0
  352. package/dist/lib/multi-system-query.js.map +1 -0
  353. package/dist/lib/orphan-detector.js +332 -0
  354. package/dist/lib/orphan-detector.js.map +1 -0
  355. package/dist/lib/password-generator.js +166 -0
  356. package/dist/lib/password-generator.js.map +1 -0
  357. package/dist/lib/path-validator.js +429 -0
  358. package/dist/lib/path-validator.js.map +1 -0
  359. package/dist/lib/query-translator.js +905 -0
  360. package/dist/lib/query-translator.js.map +1 -0
  361. package/dist/lib/queue-recovery.js +469 -0
  362. package/dist/lib/queue-recovery.js.map +1 -0
  363. package/dist/lib/redis-queue-manager.js +512 -0
  364. package/dist/lib/redis-queue-manager.js.map +1 -0
  365. package/dist/lib/reflection-archiver.js +272 -0
  366. package/dist/lib/reflection-archiver.js.map +1 -0
  367. package/dist/lib/retry-manager.js +453 -0
  368. package/dist/lib/retry-manager.js.map +1 -0
  369. package/dist/lib/retry.js +262 -0
  370. package/dist/lib/retry.js.map +1 -0
  371. package/dist/lib/schema-transform.js +695 -0
  372. package/dist/lib/schema-transform.js.map +1 -0
  373. package/dist/lib/schema-validator.js +491 -0
  374. package/dist/lib/schema-validator.js.map +1 -0
  375. package/dist/lib/skill-cache.js +297 -0
  376. package/dist/lib/skill-cache.js.map +1 -0
  377. package/dist/lib/skill-content-manager.js +337 -0
  378. package/dist/lib/skill-content-manager.js.map +1 -0
  379. package/dist/lib/skill-frontmatter-parser.js +237 -0
  380. package/dist/lib/skill-frontmatter-parser.js.map +1 -0
  381. package/dist/lib/skill-git-integration.js +275 -0
  382. package/dist/lib/skill-git-integration.js.map +1 -0
  383. package/dist/lib/skill-markdown-validator.js +396 -0
  384. package/dist/lib/skill-markdown-validator.js.map +1 -0
  385. package/dist/lib/skill-output-parser.js +312 -0
  386. package/dist/lib/skill-output-parser.js.map +1 -0
  387. package/dist/lib/unified-query-api.js +467 -0
  388. package/dist/lib/unified-query-api.js.map +1 -0
  389. package/dist/middleware/auth-middleware.js +350 -0
  390. package/dist/middleware/auth-middleware.js.map +1 -0
  391. package/dist/middleware/schema-validation.js +347 -0
  392. package/dist/middleware/schema-validation.js.map +1 -0
  393. package/dist/providers/anthropic-provider.js +1 -1
  394. package/dist/providers/anthropic-provider.js.map +1 -1
  395. package/dist/providers/provider-factory.js +2 -2
  396. package/dist/providers/provider-factory.js.map +1 -1
  397. package/dist/services/edge-case-analyzer.js +321 -0
  398. package/dist/services/edge-case-analyzer.js.map +1 -0
  399. package/dist/services/edge-case-deduplicator.js +266 -0
  400. package/dist/services/edge-case-deduplicator.js.map +1 -0
  401. package/dist/services/edge-case-detector.js +337 -0
  402. package/dist/services/edge-case-detector.js.map +1 -0
  403. package/dist/services/edge-case-tracker.js +547 -0
  404. package/dist/services/edge-case-tracker.js.map +1 -0
  405. package/dist/services/health-check-system.js +586 -0
  406. package/dist/services/health-check-system.js.map +1 -0
  407. package/dist/services/metrics-logger.js +412 -0
  408. package/dist/services/metrics-logger.js.map +1 -0
  409. package/dist/services/patch-generator.js +378 -0
  410. package/dist/services/patch-generator.js.map +1 -0
  411. package/dist/services/patch-validator.js +337 -0
  412. package/dist/services/patch-validator.js.map +1 -0
  413. package/dist/services/performance-monitor.js +811 -0
  414. package/dist/services/performance-monitor.js.map +1 -0
  415. package/dist/services/promotion-pipeline.js +918 -0
  416. package/dist/services/promotion-pipeline.js.map +1 -0
  417. package/dist/services/promotion-validator.js +394 -0
  418. package/dist/services/promotion-validator.js.map +1 -0
  419. package/dist/services/reflection-logger.js +388 -0
  420. package/dist/services/reflection-logger.js.map +1 -0
  421. package/dist/services/skill-deployment.js +472 -0
  422. package/dist/services/skill-deployment.js.map +1 -0
  423. package/dist/services/skill-loader.js +427 -0
  424. package/dist/services/skill-loader.js.map +1 -0
  425. package/dist/services/skill-promotion.js +372 -0
  426. package/dist/services/skill-promotion.js.map +1 -0
  427. package/dist/services/skill-validator.js +454 -0
  428. package/dist/services/skill-validator.js.map +1 -0
  429. package/dist/services/skill-versioning.js +244 -0
  430. package/dist/services/skill-versioning.js.map +1 -0
  431. package/dist/services/workspace-supervisor.js +597 -0
  432. package/dist/services/workspace-supervisor.js.map +1 -0
  433. package/dist/types/edge-case.js +45 -0
  434. package/dist/types/edge-case.js.map +1 -0
  435. package/package.json +201 -177
  436. package/readme/README.md +19 -4
  437. package/scripts/backup-cleanup.sh +627 -0
  438. package/scripts/cleanup-workspaces.sh +412 -0
  439. package/scripts/cleanup-yaml-configs.sh +141 -0
  440. package/scripts/deploy-approved-skills.sh +263 -0
  441. package/scripts/health-check.sh +447 -0
  442. package/scripts/log-aggregator.sh +554 -0
  443. package/scripts/log-monitor.sh +629 -0
  444. package/scripts/manage-agent-workspaces.sh +434 -0
  445. package/scripts/migrate-schema.sh +533 -0
  446. package/scripts/promote-staged-skills.sh +423 -0
  447. package/scripts/verify-no-secrets.sh +88 -35
  448. package/.claude/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
  449. package/.claude/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
  450. package/.claude/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
  451. package/.claude/skills/agent-lifecycle/SKILL.md +0 -60
  452. package/.claude/skills/agent-lifecycle/execute-lifecycle-hook.sh +0 -573
  453. package/.claude/skills/agent-lifecycle/simple-audit.sh +0 -31
  454. package/.claude/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
  455. package/.claude/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
  456. package/README.md.backup_before_replace +0 -781
  457. package/claude-assets/cfn-extras/agents/deprecated-coordinators/adaptive-coordinator.md.backup +0 -161
  458. package/claude-assets/cfn-extras/agents/deprecated-coordinators/blocking-coordinator-example.md.backup +0 -728
  459. package/claude-assets/cfn-extras/agents/deprecated-coordinators/mesh-coordinator.md.backup +0 -131
  460. package/claude-assets/skills/cfn-agent-spawning/spawn-agent.sh.backup +0 -273
  461. package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh.backup +0 -949
@@ -0,0 +1,367 @@
1
+ /**
2
+ * File Operations Utilities
3
+ *
4
+ * Provides atomic file writes and file locking mechanisms for safe concurrent operations.
5
+ * Part of Task 0.5: Implementation Tooling & Utilities (Foundation)
6
+ *
7
+ * Usage:
8
+ * await atomicWrite('/path/to/file.json', JSON.stringify(data));
9
+ * const lock = await acquireLock('/path/to/file.json');
10
+ * // ... perform operations ...
11
+ * await releaseLock(lock);
12
+ */ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { promisify } from 'util';
15
+ import { randomUUID } from 'crypto';
16
+ import { createError, ErrorCode, createTimeoutError } from './errors.js';
17
+ import { createLogger } from './logging.js';
18
+ const logger = createLogger('file-operations');
19
+ const fsWriteFile = promisify(fs.writeFile);
20
+ const fsReadFile = promisify(fs.readFile);
21
+ const fsRename = promisify(fs.rename);
22
+ const fsUnlink = promisify(fs.unlink);
23
+ const fsStat = promisify(fs.stat);
24
+ const fsMkdir = promisify(fs.mkdir);
25
+ const fsAccess = promisify(fs.access);
26
+ /**
27
+ * Default lock options
28
+ */ const DEFAULT_LOCK_OPTIONS = {
29
+ timeoutMs: 30000,
30
+ retryIntervalMs: 100,
31
+ staleTimeoutMs: 60000
32
+ };
33
+ /**
34
+ * Atomically write content to a file
35
+ *
36
+ * Pattern: Write to temp file → verify → move to target
37
+ * This ensures that the file is never left in a partially written state.
38
+ *
39
+ * @param filePath - Target file path
40
+ * @param content - Content to write
41
+ * @param encoding - File encoding (default: 'utf8')
42
+ * @returns Promise that resolves when write is complete
43
+ * @throws FILE_WRITE_FAILED if write operation fails
44
+ */ export async function atomicWrite(filePath, content, encoding = 'utf8') {
45
+ const absolutePath = path.resolve(filePath);
46
+ const dir = path.dirname(absolutePath);
47
+ const tempPath = path.join(dir, `.${path.basename(absolutePath)}.${randomUUID()}.tmp`);
48
+ try {
49
+ // Ensure directory exists
50
+ await ensureDirectory(dir);
51
+ // Write to temporary file
52
+ logger.debug('Writing to temporary file', {
53
+ tempPath
54
+ });
55
+ await fsWriteFile(tempPath, content, encoding);
56
+ // Verify write by reading back
57
+ const written = await fsReadFile(tempPath, encoding);
58
+ if (written !== content) {
59
+ throw createError(ErrorCode.FILE_WRITE_FAILED, 'Content verification failed after write', {
60
+ filePath: absolutePath,
61
+ tempPath
62
+ });
63
+ }
64
+ // Atomically move temp file to target
65
+ logger.debug('Moving temp file to target', {
66
+ tempPath,
67
+ target: absolutePath
68
+ });
69
+ await fsRename(tempPath, absolutePath);
70
+ logger.info('Atomic write completed', {
71
+ filePath: absolutePath
72
+ });
73
+ } catch (error) {
74
+ // Clean up temp file if it exists
75
+ try {
76
+ await fsUnlink(tempPath);
77
+ } catch {
78
+ // Ignore cleanup errors
79
+ }
80
+ throw createError(ErrorCode.FILE_WRITE_FAILED, `Failed to write file: ${absolutePath}`, {
81
+ filePath: absolutePath,
82
+ tempPath
83
+ }, error instanceof Error ? error : undefined);
84
+ }
85
+ }
86
+ /**
87
+ * Acquire a lock on a file
88
+ *
89
+ * Creates a lock file with metadata about the lock holder.
90
+ * Implements stale lock detection and automatic cleanup.
91
+ *
92
+ * @param filePath - File path to lock
93
+ * @param options - Lock options
94
+ * @returns FileLock object
95
+ * @throws LOCK_TIMEOUT if lock cannot be acquired within timeout
96
+ */ export async function acquireLock(filePath, options = {}) {
97
+ const opts = {
98
+ ...DEFAULT_LOCK_OPTIONS,
99
+ ...options
100
+ };
101
+ const absolutePath = path.resolve(filePath);
102
+ const lockPath = `${absolutePath}.lock`;
103
+ const lockId = randomUUID();
104
+ const startTime = Date.now();
105
+ logger.debug('Acquiring lock', {
106
+ filePath: absolutePath,
107
+ lockId
108
+ });
109
+ while(Date.now() - startTime < opts.timeoutMs){
110
+ try {
111
+ // Check if lock file exists
112
+ const lockExists = await fileExists(lockPath);
113
+ if (lockExists) {
114
+ // Check if lock is stale
115
+ const isStale = await isLockStale(lockPath, opts.staleTimeoutMs);
116
+ if (isStale) {
117
+ logger.warn('Removing stale lock', {
118
+ lockPath
119
+ });
120
+ await fsUnlink(lockPath);
121
+ } else {
122
+ // Lock is held by another process, wait and retry
123
+ await sleep(opts.retryIntervalMs);
124
+ continue;
125
+ }
126
+ }
127
+ // Attempt to create lock file
128
+ const lockData = {
129
+ filePath: absolutePath,
130
+ lockPath,
131
+ acquired: new Date(),
132
+ lockId,
133
+ pid: process.pid
134
+ };
135
+ // Write lock file atomically
136
+ await atomicWrite(lockPath, JSON.stringify(lockData, null, 2));
137
+ // Verify we actually acquired the lock (check for race condition)
138
+ const verifyData = await fsReadFile(lockPath, 'utf8');
139
+ const verifyLock = JSON.parse(verifyData);
140
+ if (verifyLock.lockId !== lockId) {
141
+ // Another process won the race
142
+ logger.debug('Lost lock race', {
143
+ lockId,
144
+ winnerId: verifyLock.lockId
145
+ });
146
+ await sleep(opts.retryIntervalMs);
147
+ continue;
148
+ }
149
+ logger.info('Lock acquired', {
150
+ filePath: absolutePath,
151
+ lockId
152
+ });
153
+ return lockData;
154
+ } catch (error) {
155
+ logger.error('Error acquiring lock', error instanceof Error ? error : undefined, {
156
+ filePath: absolutePath,
157
+ lockId
158
+ });
159
+ throw createError(ErrorCode.LOCK_TIMEOUT, `Failed to acquire lock: ${absolutePath}`, {
160
+ filePath: absolutePath,
161
+ lockPath
162
+ }, error instanceof Error ? error : undefined);
163
+ }
164
+ }
165
+ // Timeout reached
166
+ throw createTimeoutError(`acquire lock on ${absolutePath}`, opts.timeoutMs);
167
+ }
168
+ /**
169
+ * Release a file lock
170
+ *
171
+ * @param lock - FileLock object from acquireLock
172
+ * @returns Promise that resolves when lock is released
173
+ */ export async function releaseLock(lock) {
174
+ try {
175
+ // Verify lock still exists and belongs to us
176
+ const lockExists = await fileExists(lock.lockPath);
177
+ if (!lockExists) {
178
+ logger.warn('Lock file already removed', {
179
+ lockPath: lock.lockPath
180
+ });
181
+ return;
182
+ }
183
+ const currentData = await fsReadFile(lock.lockPath, 'utf8');
184
+ const currentLock = JSON.parse(currentData);
185
+ if (currentLock.lockId !== lock.lockId) {
186
+ logger.warn('Lock was taken by another process', {
187
+ ourLockId: lock.lockId,
188
+ currentLockId: currentLock.lockId
189
+ });
190
+ return;
191
+ }
192
+ // Remove lock file
193
+ await fsUnlink(lock.lockPath);
194
+ logger.info('Lock released', {
195
+ filePath: lock.filePath,
196
+ lockId: lock.lockId
197
+ });
198
+ } catch (error) {
199
+ logger.error('Error releasing lock', error instanceof Error ? error : undefined, {
200
+ lockPath: lock.lockPath,
201
+ lockId: lock.lockId
202
+ });
203
+ throw createError(ErrorCode.FILE_WRITE_FAILED, `Failed to release lock: ${lock.lockPath}`, {
204
+ lockPath: lock.lockPath,
205
+ lockId: lock.lockId
206
+ }, error instanceof Error ? error : undefined);
207
+ }
208
+ }
209
+ /**
210
+ * Execute a function with a file lock
211
+ *
212
+ * @param filePath - File path to lock
213
+ * @param fn - Function to execute while holding the lock
214
+ * @param options - Lock options
215
+ * @returns Result of the function
216
+ */ export async function withLock(filePath, fn, options = {}) {
217
+ const lock = await acquireLock(filePath, options);
218
+ try {
219
+ return await fn();
220
+ } finally{
221
+ await releaseLock(lock);
222
+ }
223
+ }
224
+ /**
225
+ * Check if a lock is stale
226
+ *
227
+ * @param lockPath - Path to lock file
228
+ * @param staleTimeoutMs - Stale timeout in milliseconds
229
+ * @returns True if lock is stale
230
+ */ async function isLockStale(lockPath, staleTimeoutMs) {
231
+ try {
232
+ const lockData = await fsReadFile(lockPath, 'utf8');
233
+ const lock = JSON.parse(lockData);
234
+ const acquiredTime = new Date(lock.acquired).getTime();
235
+ const now = Date.now();
236
+ const age = now - acquiredTime;
237
+ return age > staleTimeoutMs;
238
+ } catch {
239
+ // If we can't read the lock file, consider it stale
240
+ return true;
241
+ }
242
+ }
243
+ /**
244
+ * Check if a file exists
245
+ *
246
+ * @param filePath - File path to check
247
+ * @returns True if file exists
248
+ */ export async function fileExists(filePath) {
249
+ try {
250
+ await fsAccess(filePath, fs.constants.F_OK);
251
+ return true;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }
256
+ /**
257
+ * Ensure directory exists, creating it if necessary
258
+ *
259
+ * @param dirPath - Directory path
260
+ * @returns Promise that resolves when directory exists
261
+ */ export async function ensureDirectory(dirPath) {
262
+ try {
263
+ await fsMkdir(dirPath, {
264
+ recursive: true
265
+ });
266
+ } catch (error) {
267
+ // Ignore error if directory already exists
268
+ if (error.code !== 'EEXIST') {
269
+ throw error;
270
+ }
271
+ }
272
+ }
273
+ /**
274
+ * Sleep for specified duration
275
+ *
276
+ * @param ms - Duration in milliseconds
277
+ * @returns Promise that resolves after duration
278
+ */ function sleep(ms) {
279
+ return new Promise((resolve)=>setTimeout(resolve, ms));
280
+ }
281
+ /**
282
+ * Read file with automatic retry
283
+ *
284
+ * @param filePath - File path to read
285
+ * @param encoding - File encoding (default: 'utf8')
286
+ * @param maxRetries - Maximum retry attempts (default: 3)
287
+ * @returns File content
288
+ */ export async function readFileWithRetry(filePath, encoding = 'utf8', maxRetries = 3) {
289
+ let lastError;
290
+ for(let attempt = 0; attempt < maxRetries; attempt++){
291
+ try {
292
+ return await fsReadFile(filePath, encoding);
293
+ } catch (error) {
294
+ lastError = error instanceof Error ? error : new Error(String(error));
295
+ if (attempt < maxRetries - 1) {
296
+ await sleep(100 * Math.pow(2, attempt)); // Exponential backoff
297
+ }
298
+ }
299
+ }
300
+ throw createError(ErrorCode.FILE_NOT_FOUND, `Failed to read file after ${maxRetries} attempts: ${filePath}`, {
301
+ filePath,
302
+ maxRetries
303
+ }, lastError);
304
+ }
305
+ /**
306
+ * Write file with automatic retry
307
+ *
308
+ * @param filePath - File path to write
309
+ * @param content - Content to write
310
+ * @param encoding - File encoding (default: 'utf8')
311
+ * @param maxRetries - Maximum retry attempts (default: 3)
312
+ * @returns Promise that resolves when write is complete
313
+ */ export async function writeFileWithRetry(filePath, content, encoding = 'utf8', maxRetries = 3) {
314
+ let lastError;
315
+ for(let attempt = 0; attempt < maxRetries; attempt++){
316
+ try {
317
+ await atomicWrite(filePath, content, encoding);
318
+ return;
319
+ } catch (error) {
320
+ lastError = error instanceof Error ? error : new Error(String(error));
321
+ if (attempt < maxRetries - 1) {
322
+ await sleep(100 * Math.pow(2, attempt)); // Exponential backoff
323
+ }
324
+ }
325
+ }
326
+ throw createError(ErrorCode.FILE_WRITE_FAILED, `Failed to write file after ${maxRetries} attempts: ${filePath}`, {
327
+ filePath,
328
+ maxRetries
329
+ }, lastError);
330
+ }
331
+ /**
332
+ * Copy file atomically
333
+ *
334
+ * @param sourcePath - Source file path
335
+ * @param targetPath - Target file path
336
+ * @returns Promise that resolves when copy is complete
337
+ */ export async function atomicCopy(sourcePath, targetPath) {
338
+ const content = await fsReadFile(sourcePath, 'utf8');
339
+ await atomicWrite(targetPath, content);
340
+ logger.info('Atomic copy completed', {
341
+ source: sourcePath,
342
+ target: targetPath
343
+ });
344
+ }
345
+ /**
346
+ * Move file atomically
347
+ *
348
+ * @param sourcePath - Source file path
349
+ * @param targetPath - Target file path
350
+ * @returns Promise that resolves when move is complete
351
+ */ export async function atomicMove(sourcePath, targetPath) {
352
+ const absoluteSource = path.resolve(sourcePath);
353
+ const absoluteTarget = path.resolve(targetPath);
354
+ try {
355
+ await fsRename(absoluteSource, absoluteTarget);
356
+ logger.info('Atomic move completed', {
357
+ source: absoluteSource,
358
+ target: absoluteTarget
359
+ });
360
+ } catch (error) {
361
+ // If rename fails (e.g., cross-device), fall back to copy + delete
362
+ await atomicCopy(absoluteSource, absoluteTarget);
363
+ await fsUnlink(absoluteSource);
364
+ }
365
+ }
366
+
367
+ //# sourceMappingURL=file-operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/file-operations.ts"],"sourcesContent":["/**\r\n * File Operations Utilities\r\n *\r\n * Provides atomic file writes and file locking mechanisms for safe concurrent operations.\r\n * Part of Task 0.5: Implementation Tooling & Utilities (Foundation)\r\n *\r\n * Usage:\r\n * await atomicWrite('/path/to/file.json', JSON.stringify(data));\r\n * const lock = await acquireLock('/path/to/file.json');\r\n * // ... perform operations ...\r\n * await releaseLock(lock);\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport { promisify } from 'util';\r\nimport { randomUUID } from 'crypto';\r\nimport { createError, ErrorCode, createTimeoutError } from './errors.js';\r\nimport { createLogger } from './logging.js';\r\n\r\nconst logger = createLogger('file-operations');\r\n\r\nconst fsWriteFile = promisify(fs.writeFile);\r\nconst fsReadFile = promisify(fs.readFile);\r\nconst fsRename = promisify(fs.rename);\r\nconst fsUnlink = promisify(fs.unlink);\r\nconst fsStat = promisify(fs.stat);\r\nconst fsMkdir = promisify(fs.mkdir);\r\nconst fsAccess = promisify(fs.access);\r\n\r\n/**\r\n * File lock information\r\n */\r\nexport interface FileLock {\r\n /** Path to the file being locked */\r\n filePath: string;\r\n /** Path to the lock file */\r\n lockPath: string;\r\n /** When the lock was acquired */\r\n acquired: Date;\r\n /** Lock ID (unique identifier) */\r\n lockId: string;\r\n /** Process ID that acquired the lock */\r\n pid: number;\r\n}\r\n\r\n/**\r\n * Lock options\r\n */\r\nexport interface LockOptions {\r\n /** Timeout in milliseconds (default: 30000) */\r\n timeoutMs?: number;\r\n /** Retry interval in milliseconds (default: 100) */\r\n retryIntervalMs?: number;\r\n /** Stale lock timeout in milliseconds (default: 60000) */\r\n staleTimeoutMs?: number;\r\n}\r\n\r\n/**\r\n * Default lock options\r\n */\r\nconst DEFAULT_LOCK_OPTIONS: Required<LockOptions> = {\r\n timeoutMs: 30000, // 30 seconds\r\n retryIntervalMs: 100, // 100ms\r\n staleTimeoutMs: 60000, // 60 seconds\r\n};\r\n\r\n/**\r\n * Atomically write content to a file\r\n *\r\n * Pattern: Write to temp file → verify → move to target\r\n * This ensures that the file is never left in a partially written state.\r\n *\r\n * @param filePath - Target file path\r\n * @param content - Content to write\r\n * @param encoding - File encoding (default: 'utf8')\r\n * @returns Promise that resolves when write is complete\r\n * @throws FILE_WRITE_FAILED if write operation fails\r\n */\r\nexport async function atomicWrite(\r\n filePath: string,\r\n content: string,\r\n encoding: BufferEncoding = 'utf8'\r\n): Promise<void> {\r\n const absolutePath = path.resolve(filePath);\r\n const dir = path.dirname(absolutePath);\r\n const tempPath = path.join(dir, `.${path.basename(absolutePath)}.${randomUUID()}.tmp`);\r\n\r\n try {\r\n // Ensure directory exists\r\n await ensureDirectory(dir);\r\n\r\n // Write to temporary file\r\n logger.debug('Writing to temporary file', { tempPath });\r\n await fsWriteFile(tempPath, content, encoding);\r\n\r\n // Verify write by reading back\r\n const written = await fsReadFile(tempPath, encoding);\r\n if (written !== content) {\r\n throw createError(\r\n ErrorCode.FILE_WRITE_FAILED,\r\n 'Content verification failed after write',\r\n { filePath: absolutePath, tempPath }\r\n );\r\n }\r\n\r\n // Atomically move temp file to target\r\n logger.debug('Moving temp file to target', { tempPath, target: absolutePath });\r\n await fsRename(tempPath, absolutePath);\r\n\r\n logger.info('Atomic write completed', { filePath: absolutePath });\r\n } catch (error) {\r\n // Clean up temp file if it exists\r\n try {\r\n await fsUnlink(tempPath);\r\n } catch {\r\n // Ignore cleanup errors\r\n }\r\n\r\n throw createError(\r\n ErrorCode.FILE_WRITE_FAILED,\r\n `Failed to write file: ${absolutePath}`,\r\n { filePath: absolutePath, tempPath },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Acquire a lock on a file\r\n *\r\n * Creates a lock file with metadata about the lock holder.\r\n * Implements stale lock detection and automatic cleanup.\r\n *\r\n * @param filePath - File path to lock\r\n * @param options - Lock options\r\n * @returns FileLock object\r\n * @throws LOCK_TIMEOUT if lock cannot be acquired within timeout\r\n */\r\nexport async function acquireLock(\r\n filePath: string,\r\n options: LockOptions = {}\r\n): Promise<FileLock> {\r\n const opts = { ...DEFAULT_LOCK_OPTIONS, ...options };\r\n const absolutePath = path.resolve(filePath);\r\n const lockPath = `${absolutePath}.lock`;\r\n const lockId = randomUUID();\r\n const startTime = Date.now();\r\n\r\n logger.debug('Acquiring lock', { filePath: absolutePath, lockId });\r\n\r\n while (Date.now() - startTime < opts.timeoutMs) {\r\n try {\r\n // Check if lock file exists\r\n const lockExists = await fileExists(lockPath);\r\n\r\n if (lockExists) {\r\n // Check if lock is stale\r\n const isStale = await isLockStale(lockPath, opts.staleTimeoutMs);\r\n\r\n if (isStale) {\r\n logger.warn('Removing stale lock', { lockPath });\r\n await fsUnlink(lockPath);\r\n } else {\r\n // Lock is held by another process, wait and retry\r\n await sleep(opts.retryIntervalMs);\r\n continue;\r\n }\r\n }\r\n\r\n // Attempt to create lock file\r\n const lockData: FileLock = {\r\n filePath: absolutePath,\r\n lockPath,\r\n acquired: new Date(),\r\n lockId,\r\n pid: process.pid,\r\n };\r\n\r\n // Write lock file atomically\r\n await atomicWrite(lockPath, JSON.stringify(lockData, null, 2));\r\n\r\n // Verify we actually acquired the lock (check for race condition)\r\n const verifyData = await fsReadFile(lockPath, 'utf8');\r\n const verifyLock = JSON.parse(verifyData) as FileLock;\r\n\r\n if (verifyLock.lockId !== lockId) {\r\n // Another process won the race\r\n logger.debug('Lost lock race', { lockId, winnerId: verifyLock.lockId });\r\n await sleep(opts.retryIntervalMs);\r\n continue;\r\n }\r\n\r\n logger.info('Lock acquired', { filePath: absolutePath, lockId });\r\n return lockData;\r\n } catch (error) {\r\n logger.error('Error acquiring lock', error instanceof Error ? error : undefined, {\r\n filePath: absolutePath,\r\n lockId,\r\n });\r\n throw createError(\r\n ErrorCode.LOCK_TIMEOUT,\r\n `Failed to acquire lock: ${absolutePath}`,\r\n { filePath: absolutePath, lockPath },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n }\r\n\r\n // Timeout reached\r\n throw createTimeoutError(`acquire lock on ${absolutePath}`, opts.timeoutMs);\r\n}\r\n\r\n/**\r\n * Release a file lock\r\n *\r\n * @param lock - FileLock object from acquireLock\r\n * @returns Promise that resolves when lock is released\r\n */\r\nexport async function releaseLock(lock: FileLock): Promise<void> {\r\n try {\r\n // Verify lock still exists and belongs to us\r\n const lockExists = await fileExists(lock.lockPath);\r\n\r\n if (!lockExists) {\r\n logger.warn('Lock file already removed', { lockPath: lock.lockPath });\r\n return;\r\n }\r\n\r\n const currentData = await fsReadFile(lock.lockPath, 'utf8');\r\n const currentLock = JSON.parse(currentData) as FileLock;\r\n\r\n if (currentLock.lockId !== lock.lockId) {\r\n logger.warn('Lock was taken by another process', {\r\n ourLockId: lock.lockId,\r\n currentLockId: currentLock.lockId,\r\n });\r\n return;\r\n }\r\n\r\n // Remove lock file\r\n await fsUnlink(lock.lockPath);\r\n logger.info('Lock released', { filePath: lock.filePath, lockId: lock.lockId });\r\n } catch (error) {\r\n logger.error('Error releasing lock', error instanceof Error ? error : undefined, {\r\n lockPath: lock.lockPath,\r\n lockId: lock.lockId,\r\n });\r\n throw createError(\r\n ErrorCode.FILE_WRITE_FAILED,\r\n `Failed to release lock: ${lock.lockPath}`,\r\n { lockPath: lock.lockPath, lockId: lock.lockId },\r\n error instanceof Error ? error : undefined\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Execute a function with a file lock\r\n *\r\n * @param filePath - File path to lock\r\n * @param fn - Function to execute while holding the lock\r\n * @param options - Lock options\r\n * @returns Result of the function\r\n */\r\nexport async function withLock<T>(\r\n filePath: string,\r\n fn: () => Promise<T>,\r\n options: LockOptions = {}\r\n): Promise<T> {\r\n const lock = await acquireLock(filePath, options);\r\n\r\n try {\r\n return await fn();\r\n } finally {\r\n await releaseLock(lock);\r\n }\r\n}\r\n\r\n/**\r\n * Check if a lock is stale\r\n *\r\n * @param lockPath - Path to lock file\r\n * @param staleTimeoutMs - Stale timeout in milliseconds\r\n * @returns True if lock is stale\r\n */\r\nasync function isLockStale(lockPath: string, staleTimeoutMs: number): Promise<boolean> {\r\n try {\r\n const lockData = await fsReadFile(lockPath, 'utf8');\r\n const lock = JSON.parse(lockData) as FileLock;\r\n\r\n const acquiredTime = new Date(lock.acquired).getTime();\r\n const now = Date.now();\r\n const age = now - acquiredTime;\r\n\r\n return age > staleTimeoutMs;\r\n } catch {\r\n // If we can't read the lock file, consider it stale\r\n return true;\r\n }\r\n}\r\n\r\n/**\r\n * Check if a file exists\r\n *\r\n * @param filePath - File path to check\r\n * @returns True if file exists\r\n */\r\nexport async function fileExists(filePath: string): Promise<boolean> {\r\n try {\r\n await fsAccess(filePath, fs.constants.F_OK);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Ensure directory exists, creating it if necessary\r\n *\r\n * @param dirPath - Directory path\r\n * @returns Promise that resolves when directory exists\r\n */\r\nexport async function ensureDirectory(dirPath: string): Promise<void> {\r\n try {\r\n await fsMkdir(dirPath, { recursive: true });\r\n } catch (error) {\r\n // Ignore error if directory already exists\r\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\r\n throw error;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Sleep for specified duration\r\n *\r\n * @param ms - Duration in milliseconds\r\n * @returns Promise that resolves after duration\r\n */\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/**\r\n * Read file with automatic retry\r\n *\r\n * @param filePath - File path to read\r\n * @param encoding - File encoding (default: 'utf8')\r\n * @param maxRetries - Maximum retry attempts (default: 3)\r\n * @returns File content\r\n */\r\nexport async function readFileWithRetry(\r\n filePath: string,\r\n encoding: BufferEncoding = 'utf8',\r\n maxRetries: number = 3\r\n): Promise<string> {\r\n let lastError: Error | undefined;\r\n\r\n for (let attempt = 0; attempt < maxRetries; attempt++) {\r\n try {\r\n return await fsReadFile(filePath, encoding);\r\n } catch (error) {\r\n lastError = error instanceof Error ? error : new Error(String(error));\r\n if (attempt < maxRetries - 1) {\r\n await sleep(100 * Math.pow(2, attempt)); // Exponential backoff\r\n }\r\n }\r\n }\r\n\r\n throw createError(\r\n ErrorCode.FILE_NOT_FOUND,\r\n `Failed to read file after ${maxRetries} attempts: ${filePath}`,\r\n { filePath, maxRetries },\r\n lastError\r\n );\r\n}\r\n\r\n/**\r\n * Write file with automatic retry\r\n *\r\n * @param filePath - File path to write\r\n * @param content - Content to write\r\n * @param encoding - File encoding (default: 'utf8')\r\n * @param maxRetries - Maximum retry attempts (default: 3)\r\n * @returns Promise that resolves when write is complete\r\n */\r\nexport async function writeFileWithRetry(\r\n filePath: string,\r\n content: string,\r\n encoding: BufferEncoding = 'utf8',\r\n maxRetries: number = 3\r\n): Promise<void> {\r\n let lastError: Error | undefined;\r\n\r\n for (let attempt = 0; attempt < maxRetries; attempt++) {\r\n try {\r\n await atomicWrite(filePath, content, encoding);\r\n return;\r\n } catch (error) {\r\n lastError = error instanceof Error ? error : new Error(String(error));\r\n if (attempt < maxRetries - 1) {\r\n await sleep(100 * Math.pow(2, attempt)); // Exponential backoff\r\n }\r\n }\r\n }\r\n\r\n throw createError(\r\n ErrorCode.FILE_WRITE_FAILED,\r\n `Failed to write file after ${maxRetries} attempts: ${filePath}`,\r\n { filePath, maxRetries },\r\n lastError\r\n );\r\n}\r\n\r\n/**\r\n * Copy file atomically\r\n *\r\n * @param sourcePath - Source file path\r\n * @param targetPath - Target file path\r\n * @returns Promise that resolves when copy is complete\r\n */\r\nexport async function atomicCopy(sourcePath: string, targetPath: string): Promise<void> {\r\n const content = await fsReadFile(sourcePath, 'utf8');\r\n await atomicWrite(targetPath, content);\r\n logger.info('Atomic copy completed', { source: sourcePath, target: targetPath });\r\n}\r\n\r\n/**\r\n * Move file atomically\r\n *\r\n * @param sourcePath - Source file path\r\n * @param targetPath - Target file path\r\n * @returns Promise that resolves when move is complete\r\n */\r\nexport async function atomicMove(sourcePath: string, targetPath: string): Promise<void> {\r\n const absoluteSource = path.resolve(sourcePath);\r\n const absoluteTarget = path.resolve(targetPath);\r\n\r\n try {\r\n await fsRename(absoluteSource, absoluteTarget);\r\n logger.info('Atomic move completed', { source: absoluteSource, target: absoluteTarget });\r\n } catch (error) {\r\n // If rename fails (e.g., cross-device), fall back to copy + delete\r\n await atomicCopy(absoluteSource, absoluteTarget);\r\n await fsUnlink(absoluteSource);\r\n }\r\n}\r\n"],"names":["fs","path","promisify","randomUUID","createError","ErrorCode","createTimeoutError","createLogger","logger","fsWriteFile","writeFile","fsReadFile","readFile","fsRename","rename","fsUnlink","unlink","fsStat","stat","fsMkdir","mkdir","fsAccess","access","DEFAULT_LOCK_OPTIONS","timeoutMs","retryIntervalMs","staleTimeoutMs","atomicWrite","filePath","content","encoding","absolutePath","resolve","dir","dirname","tempPath","join","basename","ensureDirectory","debug","written","FILE_WRITE_FAILED","target","info","error","Error","undefined","acquireLock","options","opts","lockPath","lockId","startTime","Date","now","lockExists","fileExists","isStale","isLockStale","warn","sleep","lockData","acquired","pid","process","JSON","stringify","verifyData","verifyLock","parse","winnerId","LOCK_TIMEOUT","releaseLock","lock","currentData","currentLock","ourLockId","currentLockId","withLock","fn","acquiredTime","getTime","age","constants","F_OK","dirPath","recursive","code","ms","Promise","setTimeout","readFileWithRetry","maxRetries","lastError","attempt","String","Math","pow","FILE_NOT_FOUND","writeFileWithRetry","atomicCopy","sourcePath","targetPath","source","atomicMove","absoluteSource","absoluteTarget"],"mappings":"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"}
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Idempotent Write Utility
3
+ *
4
+ * Provides content-based deduplication for metrics logging using SHA256 hashing.
5
+ * Part of Task 2.3: Unified Metrics and Execution Logging
6
+ *
7
+ * Features:
8
+ * - Content-based idempotency keys (prevents duplicate metrics)
9
+ * - TTL-based cleanup (24-hour retention)
10
+ * - Atomic write tracking across databases
11
+ */ import * as crypto from 'crypto';
12
+ import { createLogger } from './logging.js';
13
+ const logger = createLogger('idempotent-write');
14
+ /**
15
+ * Default TTL for idempotency keys (24 hours)
16
+ */ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
17
+ /**
18
+ * Create idempotency key from execution metrics
19
+ *
20
+ * Key is based on: agent_id + task_id + timestamp + duration_ms
21
+ * This combination ensures uniqueness while allowing retries to be detected
22
+ *
23
+ * @param metrics - Execution metrics to create key from
24
+ * @returns SHA256 hash as idempotency key
25
+ */ export function createIdempotentKey(metrics) {
26
+ // Normalize timestamp to ISO string for consistent hashing
27
+ const timestampStr = metrics.timestamp instanceof Date ? metrics.timestamp.toISOString() : new Date(metrics.timestamp).toISOString();
28
+ // Build content string for hashing
29
+ const content = [
30
+ metrics.agent_id,
31
+ metrics.task_id,
32
+ timestampStr,
33
+ metrics.duration_ms.toString(),
34
+ metrics.status
35
+ ].join(':');
36
+ // Generate SHA256 hash
37
+ const hash = crypto.createHash('sha256').update(content).digest('hex');
38
+ logger.debug('Created idempotency key', {
39
+ agent_id: metrics.agent_id,
40
+ task_id: metrics.task_id,
41
+ key: hash.substring(0, 16) + '...'
42
+ });
43
+ return hash;
44
+ }
45
+ /**
46
+ * Check if metrics have already been written
47
+ *
48
+ * @param key - Idempotency key to check
49
+ * @param db - Database adapter
50
+ * @returns True if already written, false otherwise
51
+ */ export async function hasBeenWritten(key, db) {
52
+ try {
53
+ // Query idempotency_keys table
54
+ const result = await db.get(`idempotency_keys:${key}`);
55
+ if (!result) {
56
+ logger.debug('Idempotency key not found', {
57
+ key: key.substring(0, 16) + '...'
58
+ });
59
+ return false;
60
+ }
61
+ // Check if key has expired
62
+ const expiresAt = new Date(result.expires_at);
63
+ const now = new Date();
64
+ if (expiresAt < now) {
65
+ logger.debug('Idempotency key expired', {
66
+ key: key.substring(0, 16) + '...',
67
+ expired_at: expiresAt.toISOString()
68
+ });
69
+ return false;
70
+ }
71
+ logger.debug('Idempotency key found', {
72
+ key: key.substring(0, 16) + '...',
73
+ written_at: result.written_at
74
+ });
75
+ return true;
76
+ } catch (error) {
77
+ logger.error('Failed to check idempotency key', {
78
+ key: key.substring(0, 16) + '...',
79
+ error: error instanceof Error ? error.message : String(error)
80
+ });
81
+ // On error, assume not written to allow retry
82
+ return false;
83
+ }
84
+ }
85
+ /**
86
+ * Mark metrics as written
87
+ *
88
+ * @param key - Idempotency key
89
+ * @param db - Database adapter
90
+ * @param metricsId - Optional metrics ID to link
91
+ * @param ttlMs - Time-to-live in milliseconds (default: 24 hours)
92
+ */ export async function markWritten(key, db, metricsId, ttlMs = DEFAULT_TTL_MS) {
93
+ try {
94
+ const now = new Date();
95
+ const expiresAt = new Date(now.getTime() + ttlMs);
96
+ const idempotencyRecord = {
97
+ key,
98
+ metrics_id: metricsId,
99
+ written_at: now,
100
+ expires_at: expiresAt
101
+ };
102
+ // Insert into idempotency_keys table
103
+ await db.insert('idempotency_keys', idempotencyRecord);
104
+ logger.debug('Marked idempotency key as written', {
105
+ key: key.substring(0, 16) + '...',
106
+ metrics_id: metricsId,
107
+ expires_at: expiresAt.toISOString()
108
+ });
109
+ } catch (error) {
110
+ logger.error('Failed to mark idempotency key', {
111
+ key: key.substring(0, 16) + '...',
112
+ error: error instanceof Error ? error.message : String(error)
113
+ });
114
+ throw error;
115
+ }
116
+ }
117
+ /**
118
+ * Batch check for written metrics
119
+ *
120
+ * @param keys - Array of idempotency keys
121
+ * @param db - Database adapter
122
+ * @returns Map of key -> boolean (written status)
123
+ */ export async function batchCheckWritten(keys, db) {
124
+ const results = new Map();
125
+ try {
126
+ // Check all keys in parallel
127
+ const checks = keys.map(async (key)=>{
128
+ const written = await hasBeenWritten(key, db);
129
+ return {
130
+ key,
131
+ written
132
+ };
133
+ });
134
+ const checkResults = await Promise.all(checks);
135
+ // Build result map
136
+ checkResults.forEach(({ key, written })=>{
137
+ results.set(key, written);
138
+ });
139
+ logger.debug('Batch idempotency check complete', {
140
+ total_keys: keys.length,
141
+ already_written: Array.from(results.values()).filter((v)=>v).length
142
+ });
143
+ return results;
144
+ } catch (error) {
145
+ logger.error('Failed to batch check idempotency keys', {
146
+ total_keys: keys.length,
147
+ error: error instanceof Error ? error.message : String(error)
148
+ });
149
+ throw error;
150
+ }
151
+ }
152
+ /**
153
+ * Batch mark metrics as written
154
+ *
155
+ * @param entries - Array of { key, metricsId? } entries
156
+ * @param db - Database adapter
157
+ * @param ttlMs - Time-to-live in milliseconds (default: 24 hours)
158
+ */ export async function batchMarkWritten(entries, db, ttlMs = DEFAULT_TTL_MS) {
159
+ try {
160
+ const now = new Date();
161
+ const expiresAt = new Date(now.getTime() + ttlMs);
162
+ const records = entries.map(({ key, metricsId })=>({
163
+ key,
164
+ metrics_id: metricsId,
165
+ written_at: now,
166
+ expires_at: expiresAt
167
+ }));
168
+ // Batch insert
169
+ await db.insertMany('idempotency_keys', records);
170
+ logger.debug('Batch marked idempotency keys as written', {
171
+ total_keys: entries.length,
172
+ expires_at: expiresAt.toISOString()
173
+ });
174
+ } catch (error) {
175
+ logger.error('Failed to batch mark idempotency keys', {
176
+ total_keys: entries.length,
177
+ error: error instanceof Error ? error.message : String(error)
178
+ });
179
+ throw error;
180
+ }
181
+ }
182
+ /**
183
+ * Cleanup expired idempotency keys
184
+ *
185
+ * @param db - Database adapter
186
+ * @returns Number of keys deleted
187
+ */ export async function cleanupExpiredKeys(db) {
188
+ try {
189
+ const now = new Date();
190
+ // Query for expired keys
191
+ const expiredKeys = await db.query('idempotency_keys', [
192
+ {
193
+ field: 'expires_at',
194
+ operator: 'lt',
195
+ value: now
196
+ }
197
+ ]);
198
+ // Delete expired keys
199
+ let deletedCount = 0;
200
+ for (const key of expiredKeys){
201
+ await db.delete('idempotency_keys', key.key);
202
+ deletedCount++;
203
+ }
204
+ logger.info('Cleaned up expired idempotency keys', {
205
+ deleted_count: deletedCount
206
+ });
207
+ return deletedCount;
208
+ } catch (error) {
209
+ logger.error('Failed to cleanup expired idempotency keys', {
210
+ error: error instanceof Error ? error.message : String(error)
211
+ });
212
+ throw error;
213
+ }
214
+ }
215
+ /**
216
+ * Validate cost accuracy (within $0.001 precision)
217
+ *
218
+ * @param cost - Cost value to validate
219
+ * @returns True if cost is accurate to $0.001
220
+ */ export function validateCostAccuracy(cost) {
221
+ const rounded = parseFloat(cost.toFixed(3));
222
+ return Math.abs(cost - rounded) < 0.0001;
223
+ }
224
+ /**
225
+ * Round cost to $0.001 precision
226
+ *
227
+ * Uses standard "round half up" behavior for consistent rounding.
228
+ *
229
+ * @param cost - Cost value to round
230
+ * @returns Cost rounded to 3 decimal places
231
+ */ export function roundCost(cost) {
232
+ // Use Math.round for proper "round half up" behavior
233
+ // Multiply by 1000, round, then divide by 1000 for $0.001 precision
234
+ return Math.round(cost * 1000) / 1000;
235
+ }
236
+
237
+ //# sourceMappingURL=idempotent-write.js.map