@cyanautomation/kaseki-agent 1.4.1

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 (459) hide show
  1. package/.dockerignore +54 -0
  2. package/.eslintignore +11 -0
  3. package/.eslintrc.json +95 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +53 -0
  6. package/.github/ISSUE_TEMPLATE/security.md +51 -0
  7. package/.github/PULL_REQUEST_TEMPLATE/default.md +71 -0
  8. package/.github/dependabot.yml +38 -0
  9. package/.github/skills/dependency-cache-optimization/SKILL.md +526 -0
  10. package/.github/skills/docker-image-management/SKILL.md +532 -0
  11. package/.github/skills/frontend-design/SKILL.md +782 -0
  12. package/.github/skills/prompt-engineering/SKILL.md +360 -0
  13. package/.github/skills/quality-gate-config/SKILL.md +591 -0
  14. package/.github/skills/result-report-analysis/SKILL.md +576 -0
  15. package/.github/skills/test-automation/SKILL.md +593 -0
  16. package/.github/skills/workflow-diagnosis/SKILL.md +468 -0
  17. package/.github/workflows/build-docker-image.yml +453 -0
  18. package/.github/workflows/release.yml +68 -0
  19. package/.releaserc.json +135 -0
  20. package/CHANGELOG.md +117 -0
  21. package/CLAUDE.md +336 -0
  22. package/CONTRIBUTING.md +339 -0
  23. package/Dockerfile +217 -0
  24. package/README.md +1527 -0
  25. package/STYLE.md +521 -0
  26. package/add-js-extensions.d.ts +9 -0
  27. package/add-js-extensions.d.ts.map +1 -0
  28. package/add-js-extensions.js.map +1 -0
  29. package/dist/add-js-extensions.d.ts +9 -0
  30. package/dist/add-js-extensions.d.ts.map +1 -0
  31. package/dist/add-js-extensions.js +52 -0
  32. package/dist/add-js-extensions.js.map +1 -0
  33. package/dist/ansi-colors.d.ts +26 -0
  34. package/dist/ansi-colors.d.ts.map +1 -0
  35. package/dist/ansi-colors.js +51 -0
  36. package/dist/ansi-colors.js.map +1 -0
  37. package/dist/cli/BaseCommand.d.ts +18 -0
  38. package/dist/cli/BaseCommand.d.ts.map +1 -0
  39. package/dist/cli/BaseCommand.js +31 -0
  40. package/dist/cli/BaseCommand.js.map +1 -0
  41. package/dist/cli/KasekiCLI.d.ts +30 -0
  42. package/dist/cli/KasekiCLI.d.ts.map +1 -0
  43. package/dist/cli/KasekiCLI.js +134 -0
  44. package/dist/cli/KasekiCLI.js.map +1 -0
  45. package/dist/cli/commands/ConfigCommand.d.ts +13 -0
  46. package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
  47. package/dist/cli/commands/ConfigCommand.js +131 -0
  48. package/dist/cli/commands/ConfigCommand.js.map +1 -0
  49. package/dist/cli/commands/DoctorCommand.d.ts +45 -0
  50. package/dist/cli/commands/DoctorCommand.d.ts.map +1 -0
  51. package/dist/cli/commands/DoctorCommand.js +309 -0
  52. package/dist/cli/commands/DoctorCommand.js.map +1 -0
  53. package/dist/cli/commands/ListCommand.d.ts +9 -0
  54. package/dist/cli/commands/ListCommand.d.ts.map +1 -0
  55. package/dist/cli/commands/ListCommand.js +81 -0
  56. package/dist/cli/commands/ListCommand.js.map +1 -0
  57. package/dist/cli/commands/ReportCommand.d.ts +9 -0
  58. package/dist/cli/commands/ReportCommand.d.ts.map +1 -0
  59. package/dist/cli/commands/ReportCommand.js +98 -0
  60. package/dist/cli/commands/ReportCommand.js.map +1 -0
  61. package/dist/cli/commands/RunCommand.d.ts +13 -0
  62. package/dist/cli/commands/RunCommand.d.ts.map +1 -0
  63. package/dist/cli/commands/RunCommand.js +191 -0
  64. package/dist/cli/commands/RunCommand.js.map +1 -0
  65. package/dist/cli/commands/SecretsCommand.d.ts +9 -0
  66. package/dist/cli/commands/SecretsCommand.d.ts.map +1 -0
  67. package/dist/cli/commands/SecretsCommand.js +109 -0
  68. package/dist/cli/commands/SecretsCommand.js.map +1 -0
  69. package/dist/cli/commands/ServeCommand.d.ts +9 -0
  70. package/dist/cli/commands/ServeCommand.d.ts.map +1 -0
  71. package/dist/cli/commands/ServeCommand.js +50 -0
  72. package/dist/cli/commands/ServeCommand.js.map +1 -0
  73. package/dist/cli/commands/SetupCommand.d.ts +42 -0
  74. package/dist/cli/commands/SetupCommand.d.ts.map +1 -0
  75. package/dist/cli/commands/SetupCommand.js +249 -0
  76. package/dist/cli/commands/SetupCommand.js.map +1 -0
  77. package/dist/cli.d.ts +9 -0
  78. package/dist/cli.d.ts.map +1 -0
  79. package/dist/cli.js +130 -0
  80. package/dist/cli.js.map +1 -0
  81. package/dist/config/ConfigManager.d.ts +395 -0
  82. package/dist/config/ConfigManager.d.ts.map +1 -0
  83. package/dist/config/ConfigManager.js +446 -0
  84. package/dist/config/ConfigManager.js.map +1 -0
  85. package/dist/docker/DockerManager.d.ts +69 -0
  86. package/dist/docker/DockerManager.d.ts.map +1 -0
  87. package/dist/docker/DockerManager.js +266 -0
  88. package/dist/docker/DockerManager.js.map +1 -0
  89. package/dist/event-aggregator.d.ts +71 -0
  90. package/dist/event-aggregator.d.ts.map +1 -0
  91. package/dist/event-aggregator.js +95 -0
  92. package/dist/event-aggregator.js.map +1 -0
  93. package/dist/github-app-token.d.ts +16 -0
  94. package/dist/github-app-token.d.ts.map +1 -0
  95. package/dist/github-app-token.js +148 -0
  96. package/dist/github-app-token.js.map +1 -0
  97. package/dist/idempotency-store.d.ts +61 -0
  98. package/dist/idempotency-store.d.ts.map +1 -0
  99. package/dist/idempotency-store.js +321 -0
  100. package/dist/idempotency-store.js.map +1 -0
  101. package/dist/index.d.ts +25 -0
  102. package/dist/index.d.ts.map +1 -0
  103. package/dist/index.js +31 -0
  104. package/dist/index.js.map +1 -0
  105. package/dist/instance/InstanceManager.d.ts +81 -0
  106. package/dist/instance/InstanceManager.d.ts.map +1 -0
  107. package/dist/instance/InstanceManager.js +220 -0
  108. package/dist/instance/InstanceManager.js.map +1 -0
  109. package/dist/instance-metadata-reader.d.ts +48 -0
  110. package/dist/instance-metadata-reader.d.ts.map +1 -0
  111. package/dist/instance-metadata-reader.js +94 -0
  112. package/dist/instance-metadata-reader.js.map +1 -0
  113. package/dist/instance-state-derivation.d.ts +42 -0
  114. package/dist/instance-state-derivation.d.ts.map +1 -0
  115. package/dist/instance-state-derivation.js +133 -0
  116. package/dist/instance-state-derivation.js.map +1 -0
  117. package/dist/job-scheduler.d.ts +124 -0
  118. package/dist/job-scheduler.d.ts.map +1 -0
  119. package/dist/job-scheduler.js +992 -0
  120. package/dist/job-scheduler.js.map +1 -0
  121. package/dist/kaseki-api-client.d.ts +89 -0
  122. package/dist/kaseki-api-client.d.ts.map +1 -0
  123. package/dist/kaseki-api-client.js +405 -0
  124. package/dist/kaseki-api-client.js.map +1 -0
  125. package/dist/kaseki-api-config.d.ts +34 -0
  126. package/dist/kaseki-api-config.d.ts.map +1 -0
  127. package/dist/kaseki-api-config.js +113 -0
  128. package/dist/kaseki-api-config.js.map +1 -0
  129. package/dist/kaseki-api-routes.d.ts +13 -0
  130. package/dist/kaseki-api-routes.d.ts.map +1 -0
  131. package/dist/kaseki-api-routes.js +559 -0
  132. package/dist/kaseki-api-routes.js.map +1 -0
  133. package/dist/kaseki-api-service-wrapper.d.ts +43 -0
  134. package/dist/kaseki-api-service-wrapper.d.ts.map +1 -0
  135. package/dist/kaseki-api-service-wrapper.js +150 -0
  136. package/dist/kaseki-api-service-wrapper.js.map +1 -0
  137. package/dist/kaseki-api-service.d.ts +16 -0
  138. package/dist/kaseki-api-service.d.ts.map +1 -0
  139. package/dist/kaseki-api-service.js +143 -0
  140. package/dist/kaseki-api-service.js.map +1 -0
  141. package/dist/kaseki-api-types.d.ts +440 -0
  142. package/dist/kaseki-api-types.d.ts.map +1 -0
  143. package/dist/kaseki-api-types.js +64 -0
  144. package/dist/kaseki-api-types.js.map +1 -0
  145. package/dist/kaseki-cli-lib.d.ts +219 -0
  146. package/dist/kaseki-cli-lib.d.ts.map +1 -0
  147. package/dist/kaseki-cli-lib.js +523 -0
  148. package/dist/kaseki-cli-lib.js.map +1 -0
  149. package/dist/kaseki-cli.d.ts +38 -0
  150. package/dist/kaseki-cli.d.ts.map +1 -0
  151. package/dist/kaseki-cli.js +559 -0
  152. package/dist/kaseki-cli.js.map +1 -0
  153. package/dist/kaseki-report.d.ts +3 -0
  154. package/dist/kaseki-report.d.ts.map +1 -0
  155. package/dist/kaseki-report.js +140 -0
  156. package/dist/kaseki-report.js.map +1 -0
  157. package/dist/lib/subprocess-helpers.d.ts +98 -0
  158. package/dist/lib/subprocess-helpers.d.ts.map +1 -0
  159. package/dist/lib/subprocess-helpers.js +136 -0
  160. package/dist/lib/subprocess-helpers.js.map +1 -0
  161. package/dist/logger.d.ts +39 -0
  162. package/dist/logger.d.ts.map +1 -0
  163. package/dist/logger.js +79 -0
  164. package/dist/logger.js.map +1 -0
  165. package/dist/metrics.d.ts +19 -0
  166. package/dist/metrics.d.ts.map +1 -0
  167. package/dist/metrics.js +59 -0
  168. package/dist/metrics.js.map +1 -0
  169. package/dist/middleware/job-lookup.d.ts +27 -0
  170. package/dist/middleware/job-lookup.d.ts.map +1 -0
  171. package/dist/middleware/job-lookup.js +28 -0
  172. package/dist/middleware/job-lookup.js.map +1 -0
  173. package/dist/pi-event-filter.d.ts +3 -0
  174. package/dist/pi-event-filter.d.ts.map +1 -0
  175. package/dist/pi-event-filter.js +126 -0
  176. package/dist/pi-event-filter.js.map +1 -0
  177. package/dist/pi-progress-stream.d.ts +3 -0
  178. package/dist/pi-progress-stream.d.ts.map +1 -0
  179. package/dist/pi-progress-stream.js +205 -0
  180. package/dist/pi-progress-stream.js.map +1 -0
  181. package/dist/pi-progress-summarizer.d.ts +61 -0
  182. package/dist/pi-progress-summarizer.d.ts.map +1 -0
  183. package/dist/pi-progress-summarizer.js +246 -0
  184. package/dist/pi-progress-summarizer.js.map +1 -0
  185. package/dist/pre-flight-validator.d.ts +72 -0
  186. package/dist/pre-flight-validator.d.ts.map +1 -0
  187. package/dist/pre-flight-validator.js +513 -0
  188. package/dist/pre-flight-validator.js.map +1 -0
  189. package/dist/progress-stream-utils.d.ts +3 -0
  190. package/dist/progress-stream-utils.d.ts.map +1 -0
  191. package/dist/progress-stream-utils.js +15 -0
  192. package/dist/progress-stream-utils.js.map +1 -0
  193. package/dist/result-cache.d.ts +52 -0
  194. package/dist/result-cache.d.ts.map +1 -0
  195. package/dist/result-cache.js +134 -0
  196. package/dist/result-cache.js.map +1 -0
  197. package/dist/routes/artifact-routes.d.ts +10 -0
  198. package/dist/routes/artifact-routes.d.ts.map +1 -0
  199. package/dist/routes/artifact-routes.js +126 -0
  200. package/dist/routes/artifact-routes.js.map +1 -0
  201. package/dist/routes/log-routes.d.ts +8 -0
  202. package/dist/routes/log-routes.d.ts.map +1 -0
  203. package/dist/routes/log-routes.js +345 -0
  204. package/dist/routes/log-routes.js.map +1 -0
  205. package/dist/routes/status-routes.d.ts +8 -0
  206. package/dist/routes/status-routes.d.ts.map +1 -0
  207. package/dist/routes/status-routes.js +82 -0
  208. package/dist/routes/status-routes.js.map +1 -0
  209. package/dist/routes/webhook-routes.d.ts +6 -0
  210. package/dist/routes/webhook-routes.d.ts.map +1 -0
  211. package/dist/routes/webhook-routes.js +86 -0
  212. package/dist/routes/webhook-routes.js.map +1 -0
  213. package/dist/run-artifact-metadata-cache.d.ts +42 -0
  214. package/dist/run-artifact-metadata-cache.d.ts.map +1 -0
  215. package/dist/run-artifact-metadata-cache.js +139 -0
  216. package/dist/run-artifact-metadata-cache.js.map +1 -0
  217. package/dist/secret-value-cache.d.ts +13 -0
  218. package/dist/secret-value-cache.d.ts.map +1 -0
  219. package/dist/secret-value-cache.js +44 -0
  220. package/dist/secret-value-cache.js.map +1 -0
  221. package/dist/secrets/SecretsManager.d.ts +80 -0
  222. package/dist/secrets/SecretsManager.d.ts.map +1 -0
  223. package/dist/secrets/SecretsManager.js +306 -0
  224. package/dist/secrets/SecretsManager.js.map +1 -0
  225. package/dist/test-utils.d.ts +55 -0
  226. package/dist/test-utils.d.ts.map +1 -0
  227. package/dist/test-utils.js +48 -0
  228. package/dist/test-utils.js.map +1 -0
  229. package/dist/timestamp-tracker.d.ts +75 -0
  230. package/dist/timestamp-tracker.d.ts.map +1 -0
  231. package/dist/timestamp-tracker.js +121 -0
  232. package/dist/timestamp-tracker.js.map +1 -0
  233. package/dist/utils/failure-artifact-writer.d.ts +29 -0
  234. package/dist/utils/failure-artifact-writer.d.ts.map +1 -0
  235. package/dist/utils/failure-artifact-writer.js +157 -0
  236. package/dist/utils/failure-artifact-writer.js.map +1 -0
  237. package/dist/utils/file-helpers.d.ts +41 -0
  238. package/dist/utils/file-helpers.d.ts.map +1 -0
  239. package/dist/utils/file-helpers.js +143 -0
  240. package/dist/utils/file-helpers.js.map +1 -0
  241. package/dist/utils/http-client-factory.d.ts +46 -0
  242. package/dist/utils/http-client-factory.d.ts.map +1 -0
  243. package/dist/utils/http-client-factory.js +114 -0
  244. package/dist/utils/http-client-factory.js.map +1 -0
  245. package/dist/utils/progress-normalizer.d.ts +13 -0
  246. package/dist/utils/progress-normalizer.d.ts.map +1 -0
  247. package/dist/utils/progress-normalizer.js +57 -0
  248. package/dist/utils/progress-normalizer.js.map +1 -0
  249. package/dist/utils/response-helpers.d.ts +34 -0
  250. package/dist/utils/response-helpers.d.ts.map +1 -0
  251. package/dist/utils/response-helpers.js +78 -0
  252. package/dist/utils/response-helpers.js.map +1 -0
  253. package/dist/utils/route-helpers.d.ts +17 -0
  254. package/dist/utils/route-helpers.d.ts.map +1 -0
  255. package/dist/utils/route-helpers.js +22 -0
  256. package/dist/utils/route-helpers.js.map +1 -0
  257. package/dist/utils/status-response-builder.d.ts +23 -0
  258. package/dist/utils/status-response-builder.d.ts.map +1 -0
  259. package/dist/utils/status-response-builder.js +144 -0
  260. package/dist/utils/status-response-builder.js.map +1 -0
  261. package/dist/utils/type-guards.d.ts +37 -0
  262. package/dist/utils/type-guards.d.ts.map +1 -0
  263. package/dist/utils/type-guards.js +45 -0
  264. package/dist/utils/type-guards.js.map +1 -0
  265. package/dist/utils/utf8-helpers.d.ts +32 -0
  266. package/dist/utils/utf8-helpers.d.ts.map +1 -0
  267. package/dist/utils/utf8-helpers.js +97 -0
  268. package/dist/utils/utf8-helpers.js.map +1 -0
  269. package/dist/utils/webhook-event-builder.d.ts +26 -0
  270. package/dist/utils/webhook-event-builder.d.ts.map +1 -0
  271. package/dist/utils/webhook-event-builder.js +77 -0
  272. package/dist/utils/webhook-event-builder.js.map +1 -0
  273. package/dist/webhook-manager.d.ts +56 -0
  274. package/dist/webhook-manager.d.ts.map +1 -0
  275. package/dist/webhook-manager.js +359 -0
  276. package/dist/webhook-manager.js.map +1 -0
  277. package/docker/workspace-cache/package-lock.json +13 -0
  278. package/docker/workspace-cache/package.json +7 -0
  279. package/docker-compose.yml +53 -0
  280. package/docs/API.md +708 -0
  281. package/docs/BACKLOG.md +19 -0
  282. package/docs/BUILD_STRATEGY.md +404 -0
  283. package/docs/CLI.md +569 -0
  284. package/docs/DEPLOYMENT.md +521 -0
  285. package/docs/DEVELOPMENT.md +459 -0
  286. package/docs/DOCKER_SETUP.md +522 -0
  287. package/docs/ENHANCED_PROGRESS_LOGS.md +264 -0
  288. package/docs/IMPLEMENTATION_SUMMARY.md +549 -0
  289. package/docs/INTEGRATION_EXAMPLE.md +217 -0
  290. package/docs/NPM_SETUP.md +468 -0
  291. package/docs/PHASE1-4_IMPLEMENTATION.md +302 -0
  292. package/docs/PHASE1_COMPLETION.md +192 -0
  293. package/docs/PHASE2_COMPLETION.md +134 -0
  294. package/docs/PHASE6_MIGRATION.md +392 -0
  295. package/docs/PRINTF_SAFETY_FIX.md +282 -0
  296. package/docs/QUALITY_GATES.md +369 -0
  297. package/docs/SETUP_GUIDE.md +482 -0
  298. package/docs/TASK_PROMPT_TEMPLATES.md +533 -0
  299. package/docs/VALIDATION_FIX.md +139 -0
  300. package/docs/VERIFICATION_CHECKLIST.md +335 -0
  301. package/docs/repo-maturity.md +760 -0
  302. package/fix-tests.d.ts +9 -0
  303. package/fix-tests.d.ts.map +1 -0
  304. package/fix-tests.js.map +1 -0
  305. package/fix-tests.ts +53 -0
  306. package/jest.config.ts +31 -0
  307. package/kaseki +183 -0
  308. package/kaseki-agent.sh +1961 -0
  309. package/ops/logrotate/kaseki +10 -0
  310. package/package.json +83 -0
  311. package/perf/README.md +54 -0
  312. package/perf/pi-event-filter.benchmark.test.ts +98 -0
  313. package/run-kaseki-json.test.sh +106 -0
  314. package/run-kaseki.sh +990 -0
  315. package/scripts/allowlist-helper.sh +56 -0
  316. package/scripts/cleanup-kaseki.sh +168 -0
  317. package/scripts/deploy-pi-template.sh +293 -0
  318. package/scripts/docker-entrypoint.sh +71 -0
  319. package/scripts/dry-run-allowlist.sh +161 -0
  320. package/scripts/kaseki-activate.sh +396 -0
  321. package/scripts/kaseki-api.service +62 -0
  322. package/scripts/kaseki-container-entrypoint-wrapper.sh +119 -0
  323. package/scripts/kaseki-container-setup-remote.sh +172 -0
  324. package/scripts/kaseki-container-setup.sh +193 -0
  325. package/scripts/kaseki-healthcheck.sh +95 -0
  326. package/scripts/kaseki-install.sh +50 -0
  327. package/scripts/kaseki-maturity-score.sh +291 -0
  328. package/scripts/kaseki-performance-metrics.sh +122 -0
  329. package/scripts/kaseki-preflight.sh +270 -0
  330. package/scripts/kaseki-setup.sh +265 -0
  331. package/scripts/pi-setup-remote.sh +213 -0
  332. package/scripts/setup-github-labels.sh +42 -0
  333. package/scripts/suggest-allowlist.sh +68 -0
  334. package/scripts/templates/MULTI_HOST_DISTRIBUTED.md +337 -0
  335. package/scripts/templates/REST_API_SERVICE.md +490 -0
  336. package/scripts/templates/SINGLE_HOST_CLI.md +194 -0
  337. package/scripts/test-github-app.sh +248 -0
  338. package/src/add-js-extensions.ts +61 -0
  339. package/src/ansi-colors.test.ts +62 -0
  340. package/src/ansi-colors.ts +67 -0
  341. package/src/cli/BaseCommand.ts +40 -0
  342. package/src/cli/KasekiCLI.ts +154 -0
  343. package/src/cli/commands/ConfigCommand.ts +145 -0
  344. package/src/cli/commands/DoctorCommand.ts +329 -0
  345. package/src/cli/commands/ListCommand.ts +105 -0
  346. package/src/cli/commands/ReportCommand.ts +110 -0
  347. package/src/cli/commands/RunCommand.ts +218 -0
  348. package/src/cli/commands/SecretsCommand.ts +120 -0
  349. package/src/cli/commands/ServeCommand.ts +62 -0
  350. package/src/cli/commands/SetupCommand.ts +301 -0
  351. package/src/cli.ts +138 -0
  352. package/src/config/ConfigManager.ts +476 -0
  353. package/src/docker/DockerManager.ts +319 -0
  354. package/src/docker-entrypoint-packaging.test.ts +33 -0
  355. package/src/event-aggregator.test.ts +117 -0
  356. package/src/event-aggregator.ts +126 -0
  357. package/src/github-app-token.ts +215 -0
  358. package/src/idempotency-store.test.ts +117 -0
  359. package/src/idempotency-store.ts +385 -0
  360. package/src/index.ts +89 -0
  361. package/src/instance/InstanceManager.ts +285 -0
  362. package/src/instance-metadata-reader.test.ts +190 -0
  363. package/src/instance-metadata-reader.ts +129 -0
  364. package/src/instance-state-derivation.test.ts +263 -0
  365. package/src/instance-state-derivation.ts +148 -0
  366. package/src/job-scheduler.test.ts +1236 -0
  367. package/src/job-scheduler.ts +1117 -0
  368. package/src/kaseki-api-client.ts +488 -0
  369. package/src/kaseki-api-config.test.ts +315 -0
  370. package/src/kaseki-api-config.ts +175 -0
  371. package/src/kaseki-api-routes.test.ts +1615 -0
  372. package/src/kaseki-api-routes.ts +643 -0
  373. package/src/kaseki-api-service-wrapper.ts +188 -0
  374. package/src/kaseki-api-service.test.ts +418 -0
  375. package/src/kaseki-api-service.ts +192 -0
  376. package/src/kaseki-api-types.ts +320 -0
  377. package/src/kaseki-cli-lib.test.ts +552 -0
  378. package/src/kaseki-cli-lib.ts +760 -0
  379. package/src/kaseki-cli.ts +682 -0
  380. package/src/kaseki-report.test.ts +118 -0
  381. package/src/kaseki-report.ts +192 -0
  382. package/src/lib/subprocess-helpers.ts +177 -0
  383. package/src/logger.ts +114 -0
  384. package/src/metrics.ts +66 -0
  385. package/src/middleware/job-lookup.test.ts +113 -0
  386. package/src/middleware/job-lookup.ts +45 -0
  387. package/src/pi-event-filter.test.ts +183 -0
  388. package/src/pi-event-filter.ts +183 -0
  389. package/src/pi-progress-stream.ts +287 -0
  390. package/src/pi-progress-summarizer.test.ts +302 -0
  391. package/src/pi-progress-summarizer.ts +287 -0
  392. package/src/pre-flight-validator.test.ts +512 -0
  393. package/src/pre-flight-validator.ts +618 -0
  394. package/src/progress-stream-utils.test.ts +35 -0
  395. package/src/progress-stream-utils.ts +14 -0
  396. package/src/result-cache.test.ts +195 -0
  397. package/src/result-cache.ts +181 -0
  398. package/src/routes/artifact-routes.ts +169 -0
  399. package/src/routes/log-routes.ts +391 -0
  400. package/src/routes/status-routes.ts +92 -0
  401. package/src/routes/webhook-routes.ts +97 -0
  402. package/src/run-artifact-metadata-cache.test.ts +80 -0
  403. package/src/run-artifact-metadata-cache.ts +184 -0
  404. package/src/secret-value-cache.test.ts +66 -0
  405. package/src/secret-value-cache.ts +55 -0
  406. package/src/secrets/SecretsManager.ts +343 -0
  407. package/src/test-utils.ts +81 -0
  408. package/src/timestamp-tracker.test.ts +134 -0
  409. package/src/timestamp-tracker.ts +132 -0
  410. package/src/utils/failure-artifact-writer.ts +187 -0
  411. package/src/utils/file-helpers.test.ts +235 -0
  412. package/src/utils/file-helpers.ts +150 -0
  413. package/src/utils/http-client-factory.test.ts +245 -0
  414. package/src/utils/http-client-factory.ts +157 -0
  415. package/src/utils/progress-normalizer.test.ts +442 -0
  416. package/src/utils/progress-normalizer.ts +68 -0
  417. package/src/utils/response-helpers.test.ts +122 -0
  418. package/src/utils/response-helpers.ts +101 -0
  419. package/src/utils/route-helpers.ts +30 -0
  420. package/src/utils/status-response-builder.ts +159 -0
  421. package/src/utils/type-guards.ts +52 -0
  422. package/src/utils/utf8-helpers.ts +102 -0
  423. package/src/utils/webhook-event-builder.test.ts +143 -0
  424. package/src/utils/webhook-event-builder.ts +87 -0
  425. package/src/webhook-manager.test.ts +152 -0
  426. package/src/webhook-manager.ts +445 -0
  427. package/templates/allowlist-api-route.txt +7 -0
  428. package/templates/allowlist-comprehensive.txt +8 -0
  429. package/templates/allowlist-parser-fix.txt +6 -0
  430. package/templates/allowlist-ui-component.txt +9 -0
  431. package/templates/allowlist-utility.txt +9 -0
  432. package/test/actual-model-metadata.test.sh +102 -0
  433. package/test/dry-run.test.sh +131 -0
  434. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-0.json +1 -0
  435. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-1.json +1 -0
  436. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-invalid.json +1 -0
  437. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-0.json +1 -0
  438. package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-1.json +1 -0
  439. package/test/kaseki-api.integration.test.sh +165 -0
  440. package/test/pi-event-filter-failure.test.sh +83 -0
  441. package/test/printf-safety-focused.test.sh +99 -0
  442. package/test/printf-safety-results/results/restoration.jsonl +10 -0
  443. package/test/printf-safety-results/results/test.jsonl +0 -0
  444. package/test/printf-safety.test.sh +297 -0
  445. package/test/validation-fix.test.sh +79 -0
  446. package/test/validation-integration.test.sh +109 -0
  447. package/tests/allowlist-glob.test.sh +61 -0
  448. package/tests/dependency-cache-key.test.sh +48 -0
  449. package/tests/dependency-restore-mode.test.sh +48 -0
  450. package/tests/doctor-template-parity.test.sh +95 -0
  451. package/tests/github-operations.test.sh +142 -0
  452. package/tests/npm-install-flags.test.sh +58 -0
  453. package/tests/quality-gates.test.sh +178 -0
  454. package/tests/repo-memory.test.sh +103 -0
  455. package/tests/restore-disallowed-changes.test.sh +80 -0
  456. package/tests/validation-missing-npm-scripts.test.sh +93 -0
  457. package/tests/validation-strict-mode.test.sh +118 -0
  458. package/tsconfig.changed.json +7 -0
  459. package/tsconfig.json +39 -0
@@ -0,0 +1,122 @@
1
+ import { sendErrorResponse, buildStatusResponse, detectContentType } from './response-helpers';
2
+
3
+ describe('response-helpers', () => {
4
+ describe('sendErrorResponse', () => {
5
+ it('should send a properly formatted error response', () => {
6
+ const mockResponse = {
7
+ body: null as any,
8
+ statusValue: 200,
9
+ status: function(code: number) {
10
+ this.statusValue = code;
11
+ return this;
12
+ },
13
+ json: function(data: any) {
14
+ this.body = data;
15
+ },
16
+ };
17
+
18
+ sendErrorResponse(mockResponse as any, 404, 'Not Found', 'Run not found: abc123');
19
+
20
+ expect(mockResponse.statusValue).toBe(404);
21
+ expect(mockResponse.body).toEqual({
22
+ type: 'https://api.kaseki.local/errors#not-found',
23
+ title: 'Not Found',
24
+ status: 404,
25
+ detail: 'Run not found: abc123',
26
+ });
27
+ });
28
+
29
+ it('should convert title to kebab-case type URL', () => {
30
+ const mockResponse = {
31
+ body: null as any,
32
+ statusValue: 200,
33
+ status: function(code: number) {
34
+ this.statusValue = code;
35
+ return this;
36
+ },
37
+ json: function(data: any) {
38
+ this.body = data;
39
+ },
40
+ };
41
+
42
+ sendErrorResponse(mockResponse as any, 400, 'Bad Request', 'Invalid input');
43
+
44
+ expect(mockResponse.body.type).toBe('https://api.kaseki.local/errors#bad-request');
45
+ });
46
+ });
47
+
48
+ describe('buildStatusResponse', () => {
49
+ it('should build a complete status response', () => {
50
+ const response = buildStatusResponse({
51
+ id: 'run-123',
52
+ status: 'running',
53
+ exitCode: 0,
54
+ failureClass: 'test-failure',
55
+ correlationId: 'corr-456',
56
+ requestId: 'req-789',
57
+ error: 'test error',
58
+ resultDir: '/results/run-123',
59
+ });
60
+
61
+ expect(response).toEqual({
62
+ id: 'run-123',
63
+ status: 'running',
64
+ exitCode: 0,
65
+ failureClass: 'test-failure',
66
+ correlationId: 'corr-456',
67
+ requestId: 'req-789',
68
+ error: 'test error',
69
+ resultDir: '/results/run-123',
70
+ });
71
+ });
72
+
73
+ it('should filter out null and undefined optional fields', () => {
74
+ const response = buildStatusResponse({
75
+ id: 'run-123',
76
+ status: 'completed',
77
+ exitCode: null,
78
+ failureClass: undefined,
79
+ error: null,
80
+ });
81
+
82
+ expect(response).toEqual({
83
+ id: 'run-123',
84
+ status: 'completed',
85
+ });
86
+ });
87
+
88
+ it('should handle optional fields being omitted', () => {
89
+ const response = buildStatusResponse({
90
+ id: 'run-123',
91
+ status: 'completed',
92
+ });
93
+
94
+ expect(response).toEqual({
95
+ id: 'run-123',
96
+ status: 'completed',
97
+ });
98
+ });
99
+ });
100
+
101
+ describe('detectContentType', () => {
102
+ it('should detect JSON content type', () => {
103
+ expect(detectContentType('metadata.json')).toBe('application/json');
104
+ });
105
+
106
+ it('should detect markdown content type', () => {
107
+ expect(detectContentType('result-summary.md')).toBe('text/markdown');
108
+ });
109
+
110
+ it('should detect JSONL content type', () => {
111
+ expect(detectContentType('progress.jsonl')).toBe('application/x-jsonl');
112
+ });
113
+
114
+ it('should detect diff content type', () => {
115
+ expect(detectContentType('git.diff')).toBe('text/plain');
116
+ });
117
+
118
+ it('should default to text/plain', () => {
119
+ expect(detectContentType('unknown.xyz')).toBe('text/plain');
120
+ });
121
+ });
122
+ });
@@ -0,0 +1,101 @@
1
+ import { Response } from 'express';
2
+ import * as fs from 'fs';
3
+ import { ErrorResponse, StatusResponse } from '../kaseki-api-types';
4
+
5
+ /**
6
+ * Send a standardized error response.
7
+ * Consolidates error response generation across all endpoints.
8
+ */
9
+ export function sendErrorResponse(
10
+ res: Response,
11
+ status: number,
12
+ title: string,
13
+ detail: string
14
+ ): void {
15
+ const response: ErrorResponse = {
16
+ type: 'https://api.kaseki.local/errors#' + title.toLowerCase().replace(/\s+/g, '-'),
17
+ title,
18
+ status,
19
+ detail,
20
+ };
21
+
22
+ res.status(status).json(response);
23
+ }
24
+
25
+ /**
26
+ * Build a StatusResponse from job metadata.
27
+ * Consolidates StatusResponse construction logic used in multiple routes.
28
+ */
29
+ export function buildStatusResponse(jobData: {
30
+ id: string;
31
+ status: 'queued' | 'running' | 'completed' | 'failed';
32
+ exitCode?: number | null;
33
+ failureClass?: string | null;
34
+ correlationId?: string;
35
+ requestId?: string;
36
+ error?: string | null;
37
+ resultDir?: string;
38
+ }): StatusResponse {
39
+ const response: StatusResponse = {
40
+ id: jobData.id,
41
+ status: jobData.status,
42
+ correlationId: jobData.correlationId,
43
+ requestId: jobData.requestId,
44
+ resultDir: jobData.resultDir,
45
+ };
46
+
47
+ // Only include optional fields if they are not null
48
+ if (jobData.exitCode !== null && jobData.exitCode !== undefined) {
49
+ response.exitCode = jobData.exitCode;
50
+ }
51
+ if (jobData.failureClass !== null && jobData.failureClass !== undefined) {
52
+ response.failureClass = jobData.failureClass;
53
+ }
54
+ if (jobData.error !== null && jobData.error !== undefined) {
55
+ response.error = jobData.error;
56
+ }
57
+
58
+ return response;
59
+ }
60
+
61
+ /**
62
+ * Send a file response with appropriate headers for artifact delivery.
63
+ * Handles Content-Type detection and streaming.
64
+ */
65
+ export function sendFileResponse(
66
+ res: Response,
67
+ filePath: string,
68
+ fileName: string,
69
+ options?: { stream?: boolean }
70
+ ): void {
71
+ try {
72
+ const stat = fs.statSync(filePath);
73
+ const contentType = detectContentType(fileName);
74
+
75
+ res.setHeader('Content-Type', contentType);
76
+ res.setHeader('Content-Length', stat.size);
77
+ res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
78
+
79
+ if (options?.stream) {
80
+ const stream = fs.createReadStream(filePath);
81
+ stream.pipe(res);
82
+ } else {
83
+ const content = fs.readFileSync(filePath, 'utf-8');
84
+ res.send(content);
85
+ }
86
+ } catch (error) {
87
+ sendErrorResponse(res, 500, 'Internal Server Error', `Failed to read file: ${fileName}`);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Detect Content-Type based on file extension.
93
+ * Used for artifact responses.
94
+ */
95
+ export function detectContentType(fileName: string): string {
96
+ if (fileName.endsWith('.json')) return 'application/json';
97
+ if (fileName.endsWith('.md')) return 'text/markdown';
98
+ if (fileName.endsWith('.jsonl')) return 'application/x-jsonl';
99
+ if (fileName.endsWith('.diff')) return 'text/plain';
100
+ return 'text/plain';
101
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Route helper utilities for common patterns across API routes.
3
+ */
4
+
5
+ import { Response } from 'express';
6
+ import { JobScheduler } from '../job-scheduler';
7
+ import { Job } from '../kaseki-api-types';
8
+ import { sendErrorResponse } from './response-helpers';
9
+
10
+ /**
11
+ * Extract and validate a job ID from a request, sending an error response if not found.
12
+ * Consolidates the pattern used across artifact, log, and status routes.
13
+ *
14
+ * @param scheduler Job scheduler instance
15
+ * @param jobId Job ID to look up
16
+ * @param res Express response object (for error responses)
17
+ * @returns Job object if found, null if error response already sent
18
+ */
19
+ export function getJobOrRespond(
20
+ scheduler: JobScheduler,
21
+ jobId: string,
22
+ res: Response
23
+ ): Job | null {
24
+ const job = scheduler.getJob(jobId);
25
+ if (!job) {
26
+ sendErrorResponse(res, 404, 'Not Found', `Run not found: ${jobId}`);
27
+ return null;
28
+ }
29
+ return job;
30
+ }
@@ -0,0 +1,159 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { Job } from '../kaseki-api-types';
4
+ import { StatusResponse } from '../kaseki-api-types';
5
+ import { KasekiApiConfig } from '../kaseki-api-config';
6
+ import { JobScheduler } from '../job-scheduler';
7
+ import { getRunArtifactMetadata } from '../run-artifact-metadata-cache';
8
+ import { resolveInstanceExitCode, extractValidationFailureReason, extractQualityFailureReason } from '../instance-state-derivation';
9
+ import { toStructuredProgress } from './progress-normalizer';
10
+ import { readLastJsonlEvent } from './file-helpers';
11
+
12
+ const STATUS_KEY_FILES = ['metadata.json', 'analysis.md', 'result-summary.md', 'failure.json', 'stderr.log'] as const;
13
+
14
+ /**
15
+ * Builds StatusResponse objects with timing, progress, and artifact information.
16
+ * Encapsulates complex response building logic from status routes.
17
+ */
18
+ export class StatusResponseBuilder {
19
+ constructor(
20
+ private scheduler: JobScheduler,
21
+ private config: KasekiApiConfig
22
+ ) {}
23
+
24
+ /**
25
+ * Build a complete StatusResponse for a job.
26
+ */
27
+ buildStatus(job: Job): StatusResponse {
28
+ const runDir = job.resultDir || path.join(this.config.resultsDir, job.id);
29
+ const exitCode = this.resolveExitCode(job, runDir);
30
+ const metadata = this.readMetadata(runDir);
31
+ const validationReason = extractValidationFailureReason(metadata);
32
+ const qualityReason = extractQualityFailureReason(metadata);
33
+ const response: StatusResponse = {
34
+ id: job.id,
35
+ status: job.status,
36
+ exitCode: exitCode ?? undefined,
37
+ failureClass: job.failureClass,
38
+ validationFailureReason: validationReason ?? undefined,
39
+ qualityFailureReason: qualityReason ?? undefined,
40
+ correlationId: job.correlationId,
41
+ requestId: job.requestId,
42
+ error: job.error,
43
+ resultDir: job.resultDir,
44
+ };
45
+
46
+ this.addTimingInfo(response, job);
47
+ this.addProgressInfo(response, job);
48
+ this.addArtifactInfo(response, job);
49
+
50
+ return response;
51
+ }
52
+
53
+ private addTimingInfo(response: StatusResponse, job: Job): void {
54
+ if (job.startedAt) {
55
+ const elapsed = (job.completedAt || new Date()).getTime() - job.startedAt.getTime();
56
+ response.elapsedSeconds = Math.round(elapsed / 1000);
57
+
58
+ const timeoutSeconds = job.effectiveTimeoutSeconds ?? this.config.agentTimeoutSeconds;
59
+ const timeoutMs = timeoutSeconds * 1000;
60
+ response.timeoutRiskPercent = Math.round((elapsed / timeoutMs) * 100);
61
+ }
62
+ }
63
+
64
+ private addProgressInfo(response: StatusResponse, job: Job): void {
65
+ if (job.status !== 'running') {
66
+ return;
67
+ }
68
+
69
+ try {
70
+ const progressFile = path.join(this.config.resultsDir, job.id, 'progress.jsonl');
71
+ const lastFileEvent = readLastJsonlEvent(progressFile);
72
+ if (lastFileEvent) {
73
+ const structuredProgress = toStructuredProgress(lastFileEvent);
74
+ if (structuredProgress) {
75
+ response.progress = structuredProgress;
76
+ }
77
+ return;
78
+ }
79
+
80
+ if (typeof this.scheduler.getLiveProgressEvents === 'function') {
81
+ const liveEvents = this.scheduler.getLiveProgressEvents(job.id, 1);
82
+ const lastEvent = liveEvents[liveEvents.length - 1];
83
+ if (lastEvent) {
84
+ const structuredProgress = toStructuredProgress(lastEvent, 'running');
85
+ if (structuredProgress) {
86
+ response.progress = structuredProgress;
87
+ }
88
+ }
89
+ }
90
+ } catch {
91
+ // Ignore progress file errors; status remains resilient
92
+ }
93
+ }
94
+
95
+ private addArtifactInfo(response: StatusResponse, job: Job): void {
96
+ if (!(job.status === 'completed' || job.status === 'failed')) {
97
+ return;
98
+ }
99
+
100
+ const runDir = job.resultDir || path.join(this.config.resultsDir, job.id);
101
+ const metadata = getRunArtifactMetadata(job.id, runDir, STATUS_KEY_FILES, true);
102
+ const keyFileAvailability = STATUS_KEY_FILES.reduce(
103
+ (acc, fileName) => {
104
+ acc[fileName] = metadata[fileName]?.exists === true && metadata[fileName].size > 0;
105
+ return acc;
106
+ },
107
+ {} as Record<(typeof STATUS_KEY_FILES)[number], boolean>
108
+ );
109
+
110
+ response.artifacts = {
111
+ metadataJson: keyFileAvailability['metadata.json'],
112
+ analysisMd: keyFileAvailability['analysis.md'],
113
+ resultSummaryMd: keyFileAvailability['result-summary.md'],
114
+ failureJson: keyFileAvailability['failure.json'],
115
+ stderrLog: keyFileAvailability['stderr.log'],
116
+ availableFiles: STATUS_KEY_FILES.filter((fileName) => keyFileAvailability[fileName]),
117
+ };
118
+
119
+ if (job.status === 'failed') {
120
+ if (keyFileAvailability['failure.json']) {
121
+ response.diagnosticEntryPoint = 'failure.json';
122
+ } else if (keyFileAvailability['analysis.md']) {
123
+ response.diagnosticEntryPoint = 'analysis.md';
124
+ } else if (keyFileAvailability['result-summary.md']) {
125
+ response.diagnosticEntryPoint = 'result-summary.md';
126
+ }
127
+ }
128
+ }
129
+
130
+ private readMetadata(runDir: string): any {
131
+ try {
132
+ const metadataPath = path.join(runDir, 'metadata.json');
133
+ if (fs.existsSync(metadataPath)) {
134
+ return JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
135
+ }
136
+ } catch {
137
+ // Ignore metadata read errors
138
+ }
139
+ return {};
140
+ }
141
+
142
+ private resolveExitCode(job: Job, runDir: string): number | null {
143
+ if (job.exitCode !== undefined && job.exitCode !== null) {
144
+ return job.exitCode;
145
+ }
146
+ if (!(job.status === 'completed' || job.status === 'failed')) {
147
+ return null;
148
+ }
149
+ try {
150
+ const metadataPath = path.join(runDir, 'metadata.json');
151
+ const metadata = fs.existsSync(metadataPath)
152
+ ? JSON.parse(fs.readFileSync(metadataPath, 'utf-8'))
153
+ : {};
154
+ return resolveInstanceExitCode(runDir, metadata);
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Type guard utilities for common type checks.
3
+ * Reduces verbosity and provides reusable predicates across the codebase.
4
+ */
5
+
6
+ /**
7
+ * Check if value is a string.
8
+ */
9
+ export const isString = (value: unknown): value is string => typeof value === 'string';
10
+
11
+ /**
12
+ * Check if value is a number.
13
+ */
14
+ export const isNumber = (value: unknown): value is number => typeof value === 'number';
15
+
16
+ /**
17
+ * Check if value is a boolean.
18
+ */
19
+ export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';
20
+
21
+ /**
22
+ * Check if value is a record (plain object).
23
+ */
24
+ export const isRecord = (value: unknown): value is Record<string, unknown> => {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ };
27
+
28
+ /**
29
+ * Check if value is an array.
30
+ */
31
+ export const isArray = (value: unknown): value is unknown[] => Array.isArray(value);
32
+
33
+ /**
34
+ * Check if value is an array of strings.
35
+ */
36
+ export const isStringArray = (value: unknown): value is string[] => {
37
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
38
+ };
39
+
40
+ /**
41
+ * Check if value is an array of objects (records).
42
+ */
43
+ export const isRecordArray = (value: unknown): value is Record<string, unknown>[] => {
44
+ return Array.isArray(value) && value.every((item) => isRecord(item));
45
+ };
46
+
47
+ /**
48
+ * Check if value is an object with specific keys (shallow validation).
49
+ */
50
+ export const hasKeys = (value: unknown, ...keys: string[]): value is Record<string, unknown> => {
51
+ return isRecord(value) && keys.every((key) => key in value);
52
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * UTF-8 safe string encoding/decoding utilities.
3
+ * Handles proper UTF-8 sequence detection to prevent breaking multi-byte characters.
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+
8
+ /**
9
+ * Check if a byte is a UTF-8 continuation byte.
10
+ * Continuation bytes have the pattern 10xxxxxx (0x80-0xBF).
11
+ */
12
+ function isUtf8ContinuationByte(byte: number): boolean {
13
+ return (byte & 0xc0) === 0x80;
14
+ }
15
+
16
+ /**
17
+ * Determine the expected length of a UTF-8 sequence from its leading byte.
18
+ */
19
+ function utf8SequenceLength(leadingByte: number): number {
20
+ if ((leadingByte & 0x80) === 0) return 1; // Single-byte: 0xxxxxxx
21
+ if ((leadingByte & 0xe0) === 0xc0) return 2; // Two-byte: 110xxxxx
22
+ if ((leadingByte & 0xf0) === 0xe0) return 3; // Three-byte: 1110xxxx
23
+ if ((leadingByte & 0xf8) === 0xf0) return 4; // Four-byte: 11110xxx
24
+ return 1; // Invalid; treat as single-byte
25
+ }
26
+
27
+ /**
28
+ * Safely decode a buffer tail to UTF-8, avoiding partial UTF-8 sequences.
29
+ * If the buffer ends in the middle of a multi-byte UTF-8 sequence,
30
+ * the incomplete sequence is truncated.
31
+ *
32
+ * @param buffer Buffer to decode
33
+ * @returns UTF-8 string without incomplete sequences at the end
34
+ */
35
+ export function decodeUtf8TailSafely(buffer: Buffer): string {
36
+ let end = buffer.length;
37
+ if (end > 0) {
38
+ let continuationCount = 0;
39
+ let candidateLead = end - 1;
40
+
41
+ // Count continuation bytes at the end
42
+ while (candidateLead >= 0 && isUtf8ContinuationByte(buffer[candidateLead])) {
43
+ continuationCount++;
44
+ candidateLead--;
45
+ }
46
+
47
+ if (candidateLead < 0) {
48
+ // All bytes are continuation bytes; skip all
49
+ end = 0;
50
+ } else {
51
+ const sequenceLength = utf8SequenceLength(buffer[candidateLead]);
52
+ const expectedContinuationCount = sequenceLength - 1;
53
+
54
+ // If the continuation byte count doesn't match the expected count, truncate
55
+ if (sequenceLength > 1 && continuationCount !== expectedContinuationCount) {
56
+ end = candidateLead;
57
+ }
58
+ }
59
+ }
60
+
61
+ return buffer.subarray(0, end).toString('utf-8');
62
+ }
63
+
64
+ /**
65
+ * Extract the last N lines from a string.
66
+ *
67
+ * @param content String content to extract from
68
+ * @param maxLines Maximum number of lines to return
69
+ * @returns Last N lines of the content (or entire content if fewer lines)
70
+ */
71
+ export function tailLogByLines(content: string, maxLines: number): string {
72
+ if (maxLines <= 0) {
73
+ return '';
74
+ }
75
+
76
+ const lines = content.split(/\r?\n/);
77
+ if (lines.length <= maxLines) {
78
+ return content;
79
+ }
80
+ return lines.slice(-maxLines).join('\n');
81
+ }
82
+
83
+ /**
84
+ * Read the tail bytes of a file into a buffer.
85
+ * Useful for reading last N bytes of a large file without loading entire file.
86
+ *
87
+ * @param logFile Path to the file
88
+ * @param size Total size of the file (from fs.statSync)
89
+ * @param maxSize Maximum bytes to read
90
+ * @returns Buffer containing the tail bytes
91
+ */
92
+ export function readTailBytes(logFile: string, size: number, maxSize: number): Buffer {
93
+ const truncated = Buffer.alloc(maxSize);
94
+ const fd = fs.openSync(logFile, 'r');
95
+ try {
96
+ fs.readSync(fd, truncated, 0, maxSize, size - maxSize);
97
+ } finally {
98
+ fs.closeSync(fd);
99
+ }
100
+
101
+ return truncated;
102
+ }