@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,157 @@
1
+ /**
2
+ * HTTP client factory for creating API requests with retry logic.
3
+ * Consolidates HTTP fetch + error handling patterns across kaseki-api-client.ts.
4
+ */
5
+
6
+ export interface HttpRequestOptions {
7
+ method: 'GET' | 'POST' | 'DELETE';
8
+ headers?: Record<string, string>;
9
+ body?: string;
10
+ }
11
+
12
+ export interface RetryConfig {
13
+ maxAttempts: number;
14
+ initialDelayMs: number;
15
+ maxDelayMs: number;
16
+ }
17
+
18
+ /**
19
+ * Default retry configuration for HTTP requests.
20
+ */
21
+ const DEFAULT_RETRY_CONFIG: RetryConfig = {
22
+ maxAttempts: 3,
23
+ initialDelayMs: 1000,
24
+ maxDelayMs: 8000,
25
+ };
26
+
27
+ /**
28
+ * Creates an HTTP request factory with retry logic.
29
+ */
30
+ export class HttpClientFactory {
31
+ private retryConfig: RetryConfig;
32
+
33
+ constructor(retryConfig: Partial<RetryConfig> = {}) {
34
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
35
+ }
36
+
37
+ /**
38
+ * Create and execute an HTTP request with automatic retry on failure.
39
+ */
40
+ async request<T>(
41
+ url: string,
42
+ options: HttpRequestOptions,
43
+ parser: (data: unknown) => T,
44
+ description: string = 'HTTP request'
45
+ ): Promise<T> {
46
+ return this.retryWithBackoff(async () => {
47
+ const response = await fetch(url, options);
48
+
49
+ if (!response.ok) {
50
+ let errorDetail: string | undefined;
51
+ try {
52
+ const errorData: unknown = await response.json();
53
+ errorDetail = this.parseErrorDetail(errorData);
54
+ } catch {
55
+ // Ignore non-JSON error payloads
56
+ }
57
+ throw new Error(`${description} failed: ${errorDetail ?? response.statusText}`);
58
+ }
59
+
60
+ const data: unknown = await response.json();
61
+ return parser(data);
62
+ }, description);
63
+ }
64
+
65
+ /**
66
+ * Create and execute an HTTP request that returns text content.
67
+ */
68
+ async requestText(url: string, options: HttpRequestOptions, description: string = 'HTTP request'): Promise<string> {
69
+ return this.retryWithBackoff(async () => {
70
+ const response = await fetch(url, options);
71
+
72
+ if (!response.ok) {
73
+ throw new Error(`${description} failed: ${response.status}`);
74
+ }
75
+
76
+ return response.text();
77
+ }, description);
78
+ }
79
+
80
+ /**
81
+ * Create and execute an HTTP request that returns binary data.
82
+ */
83
+ async requestBlob(url: string, options: HttpRequestOptions, description: string = 'HTTP request'): Promise<Blob> {
84
+ return this.retryWithBackoff(async () => {
85
+ const response = await fetch(url, options);
86
+
87
+ if (!response.ok) {
88
+ throw new Error(`${description} failed: ${response.status}`);
89
+ }
90
+
91
+ return response.blob();
92
+ }, description);
93
+ }
94
+
95
+ /**
96
+ * Execute a function with exponential backoff retry.
97
+ */
98
+ private async retryWithBackoff<T>(
99
+ fn: () => Promise<T>,
100
+ description: string = 'Operation'
101
+ ): Promise<T> {
102
+ let lastError: Error | undefined;
103
+
104
+ for (let attempt = 0; attempt < this.retryConfig.maxAttempts; attempt++) {
105
+ try {
106
+ return await fn();
107
+ } catch (error) {
108
+ lastError = error instanceof Error ? error : new Error(String(error));
109
+
110
+ // Don't retry on 4xx errors (except 429 Too Many Requests)
111
+ const is4xxError =
112
+ lastError.message.includes('400') ||
113
+ lastError.message.includes('401') ||
114
+ lastError.message.includes('403') ||
115
+ lastError.message.includes('404');
116
+
117
+ if (is4xxError && !lastError.message.includes('429')) {
118
+ throw lastError;
119
+ }
120
+
121
+ // If this is the last attempt, throw
122
+ if (attempt === this.retryConfig.maxAttempts - 1) {
123
+ throw lastError;
124
+ }
125
+
126
+ // Calculate backoff with exponential increase
127
+ const delayMs = Math.min(
128
+ this.retryConfig.initialDelayMs * Math.pow(2, attempt),
129
+ this.retryConfig.maxDelayMs
130
+ );
131
+
132
+ // Wait before retrying
133
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
134
+ }
135
+ }
136
+
137
+ throw lastError || new Error(`${description} failed after ${this.retryConfig.maxAttempts} attempts`);
138
+ }
139
+
140
+ /**
141
+ * Parse error detail from a response body.
142
+ */
143
+ private parseErrorDetail(value: unknown): string | undefined {
144
+ if (!this.isRecord(value)) {
145
+ return undefined;
146
+ }
147
+ const detail = value.detail;
148
+ return typeof detail === 'string' ? detail : undefined;
149
+ }
150
+
151
+ /**
152
+ * Type guard for checking if a value is an object/record.
153
+ */
154
+ private isRecord(value: unknown): value is Record<string, unknown> {
155
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
156
+ }
157
+ }
@@ -0,0 +1,442 @@
1
+ import { normalizeProgressEvent, toStructuredProgress } from './progress-normalizer';
2
+
3
+ describe('progress-normalizer', () => {
4
+ describe('normalizeProgressEvent', () => {
5
+ it('should preserve existing message and updatedAt', () => {
6
+ const event = {
7
+ stage: 'building',
8
+ message: 'Building the project',
9
+ updatedAt: '2026-05-05T10:00:00Z',
10
+ };
11
+ const result = normalizeProgressEvent(event);
12
+ expect(result).toEqual(event);
13
+ });
14
+
15
+ it('should use detail as message when message is missing but detail is provided', () => {
16
+ const event = {
17
+ stage: 'building',
18
+ detail: 'Compiling TypeScript',
19
+ };
20
+ const result = normalizeProgressEvent(event);
21
+ expect(result.message).toBe('Compiling TypeScript');
22
+ expect(result.stage).toBe('building');
23
+ });
24
+
25
+ it('should use stage as message when both message and detail are missing', () => {
26
+ const event = {
27
+ stage: 'testing',
28
+ };
29
+ const result = normalizeProgressEvent(event);
30
+ expect(result.message).toBe('testing');
31
+ });
32
+
33
+ it('should not override message if it already exists', () => {
34
+ const event = {
35
+ stage: 'testing',
36
+ message: 'Running tests',
37
+ detail: 'This should be ignored',
38
+ };
39
+ const result = normalizeProgressEvent(event);
40
+ expect(result.message).toBe('Running tests');
41
+ });
42
+
43
+ it('should use timestamp as updatedAt when updatedAt is missing', () => {
44
+ const event = {
45
+ stage: 'running',
46
+ message: 'Agent running',
47
+ timestamp: '2026-05-05T10:30:00Z',
48
+ };
49
+ const result = normalizeProgressEvent(event);
50
+ expect(result.updatedAt).toBe('2026-05-05T10:30:00Z');
51
+ });
52
+
53
+ it('should not override updatedAt if it already exists', () => {
54
+ const event = {
55
+ stage: 'running',
56
+ updatedAt: '2026-05-05T10:30:00Z',
57
+ timestamp: '2026-05-05T10:00:00Z',
58
+ };
59
+ const result = normalizeProgressEvent(event);
60
+ expect(result.updatedAt).toBe('2026-05-05T10:30:00Z');
61
+ });
62
+
63
+ it('should handle non-string stage (should not set message)', () => {
64
+ const event = {
65
+ stage: 123,
66
+ detail: 'Some detail',
67
+ };
68
+ const result = normalizeProgressEvent(event);
69
+ expect(result.message).toBeUndefined();
70
+ });
71
+
72
+ it('should ignore non-string message and non-string detail', () => {
73
+ const event = {
74
+ stage: 'running',
75
+ message: null,
76
+ detail: undefined,
77
+ };
78
+ const result = normalizeProgressEvent(event);
79
+ expect(result.message).toBe('running');
80
+ });
81
+
82
+ it('should ignore non-string timestamp', () => {
83
+ const event = {
84
+ stage: 'running',
85
+ timestamp: 12345,
86
+ };
87
+ const result = normalizeProgressEvent(event);
88
+ expect(result.updatedAt).toBeUndefined();
89
+ });
90
+
91
+ it('should handle empty event object', () => {
92
+ const event = {};
93
+ const result = normalizeProgressEvent(event);
94
+ expect(result).toEqual({});
95
+ });
96
+ });
97
+
98
+ describe('toStructuredProgress', () => {
99
+ it('should create valid StructuredProgress with all fields', () => {
100
+ const event = {
101
+ stage: 'building',
102
+ message: 'Compiling',
103
+ percentComplete: 50,
104
+ updatedAt: '2026-05-05T10:00:00Z',
105
+ };
106
+ const result = toStructuredProgress(event);
107
+ expect(result).toEqual({
108
+ stage: 'building',
109
+ message: 'Compiling',
110
+ percentComplete: 50,
111
+ updatedAt: '2026-05-05T10:00:00Z',
112
+ });
113
+ });
114
+
115
+ it('should use fallbackStage when stage is missing', () => {
116
+ const event = { message: 'Processing' };
117
+ const result = toStructuredProgress(event);
118
+ expect(result?.stage).toBe('running');
119
+ expect(result?.message).toBe('Processing');
120
+ });
121
+
122
+ it('should use custom fallbackStage parameter', () => {
123
+ const event = { message: 'Processing' };
124
+ const result = toStructuredProgress(event, 'custom-stage');
125
+ expect(result?.stage).toBe('custom-stage');
126
+ });
127
+
128
+ it('should use fallbackStage when stage is not a string', () => {
129
+ const event = {
130
+ stage: 123,
131
+ message: 'Processing',
132
+ };
133
+ const result = toStructuredProgress(event);
134
+ expect(result?.stage).toBe('running');
135
+ });
136
+
137
+ it('should trim whitespace from stage', () => {
138
+ const event = {
139
+ stage: ' building ',
140
+ message: 'Compiling',
141
+ };
142
+ const result = toStructuredProgress(event);
143
+ expect(result?.stage).toBe('building');
144
+ });
145
+
146
+ it('should return null when stage (and fallback) is empty after trim', () => {
147
+ const event = {
148
+ stage: ' ',
149
+ };
150
+ const result = toStructuredProgress(event);
151
+ expect(result).toBeNull();
152
+ });
153
+
154
+ it('should return null when fallbackStage is empty', () => {
155
+ const event = { message: 'Processing' };
156
+ const result = toStructuredProgress(event, '');
157
+ expect(result).toBeNull();
158
+ });
159
+
160
+ it('should prioritize message over detail', () => {
161
+ const event = {
162
+ stage: 'building',
163
+ message: 'Using this message',
164
+ detail: 'Not this one',
165
+ };
166
+ const result = toStructuredProgress(event);
167
+ expect(result?.message).toBe('Using this message');
168
+ });
169
+
170
+ it('should use detail when message is missing', () => {
171
+ const event = {
172
+ stage: 'building',
173
+ detail: 'Using this detail',
174
+ };
175
+ const result = toStructuredProgress(event);
176
+ expect(result?.message).toBe('Using this detail');
177
+ });
178
+
179
+ it('should use stage as message fallback', () => {
180
+ const event = {
181
+ stage: 'building',
182
+ };
183
+ const result = toStructuredProgress(event);
184
+ expect(result?.message).toBe('building');
185
+ });
186
+
187
+ it('should ignore non-string message', () => {
188
+ const event = {
189
+ stage: 'building',
190
+ message: 123,
191
+ detail: 'Detail message',
192
+ };
193
+ const result = toStructuredProgress(event);
194
+ expect(result?.message).toBe('Detail message');
195
+ });
196
+
197
+ it('should ignore non-string detail', () => {
198
+ const event = {
199
+ stage: 'building',
200
+ message: 'Main message',
201
+ detail: null,
202
+ };
203
+ const result = toStructuredProgress(event);
204
+ expect(result?.message).toBe('Main message');
205
+ });
206
+
207
+ it('should prioritize percentComplete over percent', () => {
208
+ const event = {
209
+ stage: 'running',
210
+ percentComplete: 75,
211
+ percent: 50,
212
+ };
213
+ const result = toStructuredProgress(event);
214
+ expect(result?.percentComplete).toBe(75);
215
+ });
216
+
217
+ it('should use percent when percentComplete is missing', () => {
218
+ const event = {
219
+ stage: 'running',
220
+ percent: 60,
221
+ };
222
+ const result = toStructuredProgress(event);
223
+ expect(result?.percentComplete).toBe(60);
224
+ });
225
+
226
+ it('should ignore non-number percentComplete', () => {
227
+ const event = {
228
+ stage: 'running',
229
+ percentComplete: '75',
230
+ percent: 50,
231
+ };
232
+ const result = toStructuredProgress(event);
233
+ expect(result?.percentComplete).toBe(50);
234
+ });
235
+
236
+ it('should ignore non-number percent', () => {
237
+ const event = {
238
+ stage: 'running',
239
+ percent: '60',
240
+ };
241
+ const result = toStructuredProgress(event);
242
+ expect(result?.percentComplete).toBeUndefined();
243
+ });
244
+
245
+ it('should prioritize updatedAt over timestamp', () => {
246
+ const event = {
247
+ stage: 'running',
248
+ updatedAt: '2026-05-05T10:30:00Z',
249
+ timestamp: '2026-05-05T10:00:00Z',
250
+ };
251
+ const result = toStructuredProgress(event);
252
+ expect(result?.updatedAt).toBe('2026-05-05T10:30:00Z');
253
+ });
254
+
255
+ it('should use timestamp when updatedAt is missing', () => {
256
+ const event = {
257
+ stage: 'running',
258
+ timestamp: '2026-05-05T10:00:00Z',
259
+ };
260
+ const result = toStructuredProgress(event);
261
+ expect(result?.updatedAt).toBe('2026-05-05T10:00:00Z');
262
+ });
263
+
264
+ it('should ignore non-string updatedAt', () => {
265
+ const event = {
266
+ stage: 'running',
267
+ updatedAt: 12345,
268
+ timestamp: '2026-05-05T10:00:00Z',
269
+ };
270
+ const result = toStructuredProgress(event);
271
+ expect(result?.updatedAt).toBe('2026-05-05T10:00:00Z');
272
+ });
273
+
274
+ it('should ignore non-string timestamp', () => {
275
+ const event = {
276
+ stage: 'running',
277
+ timestamp: 12345,
278
+ };
279
+ const result = toStructuredProgress(event);
280
+ expect(result?.updatedAt).toBeUndefined();
281
+ });
282
+
283
+ it('should handle undefined percentComplete and timestamp', () => {
284
+ const event = {
285
+ stage: 'running',
286
+ message: 'In progress',
287
+ };
288
+ const result = toStructuredProgress(event);
289
+ expect(result).toEqual({
290
+ stage: 'running',
291
+ message: 'In progress',
292
+ percentComplete: undefined,
293
+ updatedAt: undefined,
294
+ });
295
+ });
296
+
297
+ it('should handle percentComplete of 0', () => {
298
+ const event = {
299
+ stage: 'running',
300
+ percentComplete: 0,
301
+ };
302
+ const result = toStructuredProgress(event);
303
+ expect(result?.percentComplete).toBe(0);
304
+ });
305
+
306
+ it('should handle percentComplete of 100', () => {
307
+ const event = {
308
+ stage: 'completed',
309
+ percentComplete: 100,
310
+ };
311
+ const result = toStructuredProgress(event);
312
+ expect(result?.percentComplete).toBe(100);
313
+ });
314
+
315
+ it('should return null when stage is empty string (no fallback even with fallback parameter)', () => {
316
+ const event = {
317
+ stage: '',
318
+ };
319
+ const result = toStructuredProgress(event, 'fallback-stage');
320
+ expect(result).toBeNull();
321
+ });
322
+
323
+ });
324
+
325
+ describe('integration: normalizeProgressEvent → toStructuredProgress', () => {
326
+ it('should work together with field remapping without mutating the source event', () => {
327
+ const event = {
328
+ stage: 'testing',
329
+ detail: 'Running unit tests',
330
+ percent: 75,
331
+ timestamp: '2026-05-05T10:00:00Z',
332
+ runId: 'run-123',
333
+ };
334
+
335
+ const normalized = normalizeProgressEvent(event);
336
+ const structured = toStructuredProgress(normalized);
337
+
338
+ expect(normalized).toEqual({
339
+ stage: 'testing',
340
+ detail: 'Running unit tests',
341
+ percent: 75,
342
+ timestamp: '2026-05-05T10:00:00Z',
343
+ runId: 'run-123',
344
+ message: 'Running unit tests',
345
+ updatedAt: '2026-05-05T10:00:00Z',
346
+ });
347
+ const originalEvent = { ...event };
348
+ expect(event).toEqual(originalEvent);
349
+ expect(event).not.toHaveProperty('message');
350
+ expect(event).not.toHaveProperty('updatedAt');
351
+ expect(structured).toEqual({
352
+ stage: 'testing',
353
+ message: 'Running unit tests',
354
+ percentComplete: 75,
355
+ updatedAt: '2026-05-05T10:00:00Z',
356
+ });
357
+ });
358
+
359
+ it('should preserve client-visible fields from a progress.jsonl event', () => {
360
+ const line = JSON.stringify({
361
+ timestamp: '2026-05-05T10:00:00.000Z',
362
+ updatedAt: '2026-05-05T10:00:00.000Z',
363
+ stage: 'pi coding agent',
364
+ message: 'working; events=5, tool starts=2, tool ends=1',
365
+ percentComplete: 45,
366
+ counts: { assistant_message: 2, tool_start: 2, tool_end: 1 },
367
+ toolStartCount: 2,
368
+ toolEndCount: 1,
369
+ messageUpdateCount: 2,
370
+ reason: 'events',
371
+ });
372
+
373
+ const normalized = normalizeProgressEvent(JSON.parse(line));
374
+ const structured = toStructuredProgress(normalized);
375
+
376
+ expect(normalized).toMatchObject({
377
+ timestamp: '2026-05-05T10:00:00.000Z',
378
+ updatedAt: '2026-05-05T10:00:00.000Z',
379
+ stage: 'pi coding agent',
380
+ message: 'working; events=5, tool starts=2, tool ends=1',
381
+ percentComplete: 45,
382
+ counts: { assistant_message: 2, tool_start: 2, tool_end: 1 },
383
+ toolStartCount: 2,
384
+ toolEndCount: 1,
385
+ messageUpdateCount: 2,
386
+ reason: 'events',
387
+ });
388
+
389
+ // Structured status progress is intentionally limited to the public
390
+ // StructuredProgress contract; richer progress.jsonl/log fields remain
391
+ // client-visible through the progress/events log endpoints.
392
+ expect(structured).toEqual({
393
+ stage: 'pi coding agent',
394
+ message: 'working; events=5, tool starts=2, tool ends=1',
395
+ percentComplete: 45,
396
+ updatedAt: '2026-05-05T10:00:00.000Z',
397
+ });
398
+ });
399
+
400
+ it('should strip shell diagnostics accidentally interleaved into live progress messages', () => {
401
+ const structured = toStructuredProgress({
402
+ stage: 'dependency install info',
403
+ message: 'cache hit flags=--ignore-scripts/usr/local/bin/kaseki-agent: line 378: /usr/local/bin/scripts/allowlist-helper.sh: No such file or directory',
404
+ timestamp: '2026-05-07T12:24:48.758Z',
405
+ });
406
+
407
+ expect(structured).toEqual({
408
+ stage: 'dependency install info',
409
+ message: 'cache hit flags=--ignore-scripts',
410
+ percentComplete: undefined,
411
+ updatedAt: '2026-05-07T12:24:48.758Z',
412
+ });
413
+ });
414
+
415
+ it('should strip git clone stderr accidentally interleaved into live progress messages', () => {
416
+ const structured = toStructuredProgress({
417
+ stage: 'pi coding agent',
418
+ message: "working; events=1079, tool starts=8, tool ends=8Cloning into '/workspace/repo'...",
419
+ timestamp: '2026-05-07T12:56:25.853Z',
420
+ });
421
+
422
+ expect(structured).toEqual({
423
+ stage: 'pi coding agent',
424
+ message: 'working; events=1079, tool starts=8, tool ends=8',
425
+ percentComplete: undefined,
426
+ updatedAt: '2026-05-07T12:56:25.853Z',
427
+ });
428
+ });
429
+
430
+ it('should handle missing fields gracefully in pipeline', () => {
431
+ const event = {
432
+ stage: 'running',
433
+ };
434
+ const normalized = normalizeProgressEvent(event);
435
+ const structured = toStructuredProgress(normalized);
436
+ expect(structured?.stage).toBe('running');
437
+ expect(structured?.message).toBe('running');
438
+ expect(structured?.percentComplete).toBeUndefined();
439
+ expect(structured?.updatedAt).toBeUndefined();
440
+ });
441
+ });
442
+ });
@@ -0,0 +1,68 @@
1
+ import { StructuredProgress } from '../kaseki-api-types';
2
+
3
+ const SHELL_DIAGNOSTIC_PATTERN = /\/[^\s:]+(?:\/[^\s:]+)*:\s+line\s+\d+:\s+.*$/;
4
+ const GIT_CLONE_STDERR_PATTERN = /Cloning into '[^']+'\.\.\.$/;
5
+
6
+ export function sanitizeProgressMessage(message: string): string {
7
+ return message
8
+ .replace(SHELL_DIAGNOSTIC_PATTERN, '')
9
+ .replace(GIT_CLONE_STDERR_PATTERN, '')
10
+ .replace(/\s+/g, ' ')
11
+ .trim();
12
+ }
13
+
14
+ /**
15
+ * Normalize a progress event by ensuring common fields are standardized.
16
+ * Ensures message is populated from stage/detail, and timestamp is normalized to updatedAt.
17
+ */
18
+ export function normalizeProgressEvent(event: Record<string, unknown>): Record<string, unknown> {
19
+ const normalized: Record<string, unknown> = { ...event };
20
+ if (typeof normalized.stage === 'string') {
21
+ if (typeof normalized.message !== 'string' && typeof normalized.detail === 'string') {
22
+ normalized.message = sanitizeProgressMessage(normalized.detail);
23
+ }
24
+ if (typeof normalized.message !== 'string') {
25
+ normalized.message = normalized.stage;
26
+ } else {
27
+ normalized.message = sanitizeProgressMessage(normalized.message);
28
+ }
29
+ }
30
+ if (typeof normalized.updatedAt !== 'string' && typeof normalized.timestamp === 'string') {
31
+ normalized.updatedAt = normalized.timestamp;
32
+ }
33
+ return normalized;
34
+ }
35
+
36
+ /**
37
+ * Convert a progress event to a StructuredProgress object.
38
+ * Always ensures stage is present; returns null only if event is invalid.
39
+ */
40
+ export function toStructuredProgress(
41
+ event: Record<string, unknown>,
42
+ fallbackStage: string = 'running'
43
+ ): StructuredProgress | null {
44
+ const stage = typeof event.stage === 'string' ? event.stage.trim() : fallbackStage;
45
+ if (!stage) {
46
+ return null;
47
+ }
48
+
49
+ const message =
50
+ typeof event.message === 'string'
51
+ ? sanitizeProgressMessage(event.message)
52
+ : typeof event.detail === 'string'
53
+ ? sanitizeProgressMessage(event.detail)
54
+ : undefined;
55
+
56
+ const numericPercent = typeof event.percentComplete === 'number' ? event.percentComplete : undefined;
57
+ const percentFromPercent = typeof event.percent === 'number' ? event.percent : undefined;
58
+ const percentComplete = numericPercent ?? percentFromPercent;
59
+ const updatedAt =
60
+ typeof event.updatedAt === 'string' ? event.updatedAt : typeof event.timestamp === 'string' ? event.timestamp : undefined;
61
+
62
+ return {
63
+ stage,
64
+ percentComplete,
65
+ message: message || stage,
66
+ updatedAt,
67
+ };
68
+ }