@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,618 @@
1
+ import { spawn } from 'child_process';
2
+ import { ValidationCheck, ValidationResponse, RunRequest } from './kaseki-api-types';
3
+ import { createEventLogger, EventLogger } from './logger';
4
+
5
+ /**
6
+ * Convert glob-style pattern to regex.
7
+ * Supports *, **, ?, and [abc] patterns.
8
+ */
9
+ export function globToRegex(pattern: string): RegExp {
10
+ // Use placeholders for all special sequences to avoid double-replacement
11
+ let regex = pattern;
12
+
13
+ // First, escape all regex special chars
14
+ regex = regex.replace(/[.+^${}()|[\]\\-]/g, '\\$&');
15
+
16
+ // Use placeholders for glob wildcards BEFORE replacing their components
17
+ regex = regex.replace(/\*\*/g, '##DBL_STAR##');
18
+ regex = regex.replace(/\*/g, '##STAR##');
19
+ regex = regex.replace(/\?/g, '##QUEST##');
20
+
21
+ // Now convert placeholders to regex patterns
22
+ // Handle ** in different contexts
23
+ regex = regex.replace(/\/##DBL_STAR##\//g, '/(?:.*)/?'); // /../
24
+ regex = regex.replace(/##DBL_STAR##\//g, '(?:.*)?'); // **/ at start
25
+ regex = regex.replace(/\/##DBL_STAR##/g, '(?:.*)?'); // /** at end
26
+ regex = regex.replace(/##DBL_STAR##/g, '.*'); // ** alone
27
+
28
+ // Handle single *
29
+ regex = regex.replace(/##STAR##/g, '[^/]*');
30
+
31
+ // Handle ?
32
+ regex = regex.replace(/##QUEST##/g, '.');
33
+
34
+ return new RegExp(`^${regex}$`);
35
+ }
36
+
37
+ /**
38
+ * Test if a file path matches any of the allowed patterns.
39
+ */
40
+ export function testPathAgainstPatterns(filePath: string, patterns: string[]): boolean {
41
+ if (patterns.length === 0) return true;
42
+ for (const pattern of patterns) {
43
+ const regex = globToRegex(pattern);
44
+ if (regex.test(filePath)) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+
51
+ /**
52
+ * Validate allowlist patterns by testing against common file paths.
53
+ */
54
+ export function validateAllowlistPatternMatching(
55
+ patterns: string[]
56
+ ): { isValid: boolean; warnings: string[]; testResults: { pattern: string; matches: number }[] } {
57
+ const warnings: string[] = [];
58
+ const testResults: { pattern: string; matches: number }[] = [];
59
+ const sampleFiles = [
60
+ 'package.json', 'src/index.ts', 'src/lib/parser.ts', 'src/lib/parser.tsx',
61
+ 'src/utils/helpers.ts', 'tests/parser.test.ts', 'tests/parser.validation.ts',
62
+ 'tests/unit/foo.test.ts', 'docs/README.md', 'docs/API.md', '.github/workflows/ci.yml',
63
+ 'dist/index.js', 'README.md', 'CHANGELOG.md', '.eslintrc.json', 'tsconfig.json',
64
+ ];
65
+
66
+ if (patterns.length === 0) {
67
+ return { isValid: true, warnings: [], testResults: [] };
68
+ }
69
+
70
+ for (const pattern of patterns) {
71
+ if (!pattern || !pattern.trim()) {
72
+ warnings.push('Empty pattern detected');
73
+ continue;
74
+ }
75
+
76
+ // Warn about obviously broad patterns
77
+ if (pattern === '*' || pattern === '**' || pattern === '/**') {
78
+ warnings.push(`Pattern '${pattern}' is very broad and may allow too many files`);
79
+ testResults.push({ pattern, matches: sampleFiles.length });
80
+ continue;
81
+ }
82
+
83
+ const matchCount = sampleFiles.filter((file) => testPathAgainstPatterns(file, [pattern])).length;
84
+ testResults.push({ pattern, matches: matchCount });
85
+ if (matchCount === sampleFiles.length) {
86
+ warnings.push(`Pattern '${pattern}' is too broad`);
87
+ }
88
+ if (matchCount === 0) {
89
+ warnings.push(`Pattern '${pattern}' doesn't match any sample files`);
90
+ }
91
+ }
92
+
93
+ return { isValid: warnings.length === 0, warnings, testResults };
94
+ }
95
+
96
+ /**
97
+ * Pre-flight validator performs checks on job requests before submission.
98
+ * Helps catch configuration errors early and provides better diagnostics.
99
+ */
100
+ type PreFlightCacheEntry = {
101
+ expiresAt: number;
102
+ promise?: Promise<ValidationResponse>;
103
+ response?: ValidationResponse;
104
+ };
105
+
106
+ type GitLsRemoteResult = {
107
+ code: number | null;
108
+ durationMs: number;
109
+ output: string;
110
+ timedOut: boolean;
111
+ error?: Error;
112
+ };
113
+
114
+ export class PreFlightValidator {
115
+ private logger: EventLogger;
116
+ private gitCheckTimeoutMs = 5000;
117
+ private maxRepoSizeBytes = 5 * 1024 * 1024 * 1024; // 5 GB
118
+ private cache = new Map<string, PreFlightCacheEntry>();
119
+ private cacheTtlMs: number;
120
+ private cacheMaxEntries: number;
121
+
122
+ constructor() {
123
+ this.logger = createEventLogger('pre-flight-validator');
124
+ this.cacheTtlMs = this.parsePositiveIntegerEnv('KASEKI_PREFLIGHT_CACHE_TTL_SECONDS', 30) * 1000;
125
+ this.cacheMaxEntries = this.parsePositiveIntegerEnv('KASEKI_PREFLIGHT_CACHE_MAX_ENTRIES', 100);
126
+ }
127
+
128
+ /**
129
+ * Run all pre-flight validation checks.
130
+ */
131
+ async validate(request: RunRequest): Promise<ValidationResponse> {
132
+ if (this.cacheTtlMs <= 0 || this.cacheMaxEntries <= 0) {
133
+ return this.runValidation(request);
134
+ }
135
+
136
+ const cacheKey = this.getCacheKey(request);
137
+ const cached = this.getCachedResponse(cacheKey);
138
+
139
+ if (cached) {
140
+ return cached;
141
+ }
142
+
143
+ const expiresAt = Date.now() + this.cacheTtlMs;
144
+ const promise = this.runValidation(request)
145
+ .then((response) => {
146
+ const cachedResponse = this.cloneValidationResponse(response);
147
+ const current = this.cache.get(cacheKey);
148
+
149
+ if (current?.promise === promise) {
150
+ this.cache.set(cacheKey, {
151
+ expiresAt: Date.now() + this.cacheTtlMs,
152
+ response: cachedResponse,
153
+ });
154
+ this.enforceCacheLimit();
155
+ }
156
+
157
+ return response;
158
+ })
159
+ .catch((error) => {
160
+ const current = this.cache.get(cacheKey);
161
+ if (current?.promise === promise) {
162
+ this.cache.delete(cacheKey);
163
+ }
164
+ throw error;
165
+ });
166
+
167
+ this.cache.set(cacheKey, { expiresAt, promise });
168
+ this.enforceCacheLimit();
169
+
170
+ return promise;
171
+ }
172
+
173
+ private async runValidation(request: RunRequest): Promise<ValidationResponse> {
174
+ const checks: ValidationCheck[] = [];
175
+ const warnings: string[] = [];
176
+ const errors: string[] = [];
177
+ const ref = request.ref || 'main';
178
+ const gitResult = await this.lsRemoteHeadsAndTags(request.repoUrl);
179
+
180
+ // Check 1: Git repository reachability
181
+ const reachableCheck = this.checkGitReachability(request.repoUrl, gitResult);
182
+ checks.push(reachableCheck);
183
+ if (reachableCheck.status === 'fail') {
184
+ errors.push(reachableCheck.message);
185
+ }
186
+
187
+ // Check 2: Git ref exists (only if repo is reachable)
188
+ if (reachableCheck.status !== 'fail') {
189
+ const refCheck = this.checkGitRef(request.repoUrl, ref, gitResult);
190
+ checks.push(refCheck);
191
+ if (refCheck.status === 'fail') {
192
+ errors.push(refCheck.message);
193
+ }
194
+ }
195
+
196
+ // Check 3: Repository size estimation
197
+ const sizeCheck = this.checkRepoSize(gitResult);
198
+ checks.push(sizeCheck);
199
+ if (sizeCheck.status === 'fail') {
200
+ errors.push(sizeCheck.message);
201
+ } else if (sizeCheck.status === 'warning') {
202
+ warnings.push(sizeCheck.message);
203
+ }
204
+
205
+ // Check 4: Validation commands syntax
206
+ if (request.validationCommands && request.validationCommands.length > 0) {
207
+ const cmdCheck = this.validateCommandsSyntax(request.validationCommands);
208
+ checks.push(cmdCheck);
209
+ if (cmdCheck.status === 'fail') {
210
+ errors.push(cmdCheck.message);
211
+ }
212
+ }
213
+
214
+ // Check 5: Changed files allowlist patterns
215
+ if (request.changedFilesAllowlist && request.changedFilesAllowlist.length > 0) {
216
+ const patternCheck = this.validateAllowlistPatterns(request.changedFilesAllowlist);
217
+ checks.push(patternCheck);
218
+ if (patternCheck.status === 'warning') {
219
+ warnings.push(patternCheck.message);
220
+ }
221
+ }
222
+
223
+ // Check 6: Max diff bytes sanity
224
+ const diffCheck = this.validateDiffBytes(request.maxDiffBytes);
225
+ checks.push(diffCheck);
226
+ if (diffCheck.status === 'warning') {
227
+ warnings.push(diffCheck.message);
228
+ }
229
+
230
+ const isValid = errors.length === 0;
231
+
232
+ this.logger.event('pre_flight_validation', {
233
+ repoUrl: request.repoUrl,
234
+ isValid,
235
+ checksRun: checks.length,
236
+ checksFailed: checks.filter((c) => c.status === 'fail').length,
237
+ checksWarning: checks.filter((c) => c.status === 'warning').length,
238
+ });
239
+
240
+ return {
241
+ isValid,
242
+ checks,
243
+ warnings,
244
+ errors,
245
+ estimatedDurationSeconds: isValid ? this.estimateDuration(request) : undefined,
246
+ };
247
+ }
248
+
249
+ private getCacheKey(request: RunRequest): string {
250
+ return JSON.stringify([request.repoUrl, request.ref || 'main']);
251
+ }
252
+
253
+ private getCachedResponse(cacheKey: string): Promise<ValidationResponse> | ValidationResponse | undefined {
254
+ const entry = this.cache.get(cacheKey);
255
+ const now = Date.now();
256
+
257
+ if (!entry) {
258
+ this.pruneExpiredCacheEntries(now);
259
+ return undefined;
260
+ }
261
+
262
+ if (entry.expiresAt <= now) {
263
+ this.cache.delete(cacheKey);
264
+ this.pruneExpiredCacheEntries(now);
265
+ return undefined;
266
+ }
267
+
268
+ if (entry.response) {
269
+ // Refresh insertion order so frequently used entries are less likely to be evicted.
270
+ this.cache.delete(cacheKey);
271
+ this.cache.set(cacheKey, entry);
272
+ return this.cloneValidationResponse(entry.response);
273
+ }
274
+
275
+ return entry.promise?.then((response) => this.cloneValidationResponse(response));
276
+ }
277
+
278
+ private pruneExpiredCacheEntries(now = Date.now()): void {
279
+ for (const [key, entry] of this.cache) {
280
+ if (entry.expiresAt <= now) {
281
+ this.cache.delete(key);
282
+ }
283
+ }
284
+ }
285
+
286
+ private enforceCacheLimit(): void {
287
+ this.pruneExpiredCacheEntries();
288
+
289
+ while (this.cache.size > this.cacheMaxEntries) {
290
+ const oldestKey = this.cache.keys().next().value as string | undefined;
291
+ if (!oldestKey) {
292
+ break;
293
+ }
294
+ this.cache.delete(oldestKey);
295
+ }
296
+ }
297
+
298
+ private cloneValidationResponse(response: ValidationResponse): ValidationResponse {
299
+ return {
300
+ ...response,
301
+ checks: response.checks.map((check) => ({ ...check })),
302
+ warnings: [...response.warnings],
303
+ errors: [...response.errors],
304
+ };
305
+ }
306
+
307
+ private parsePositiveIntegerEnv(name: string, defaultValue: number): number {
308
+ const value = process.env[name];
309
+
310
+ if (value === undefined || value.trim() === '') {
311
+ return defaultValue;
312
+ }
313
+
314
+ const parsed = Number.parseInt(value, 10);
315
+ if (!Number.isFinite(parsed) || parsed < 0) {
316
+ return defaultValue;
317
+ }
318
+
319
+ return parsed;
320
+ }
321
+
322
+ private lsRemoteHeadsAndTags(repoUrl: string): Promise<GitLsRemoteResult> {
323
+ return new Promise((resolve) => {
324
+ const startTime = Date.now();
325
+ const proc = spawn('git', ['ls-remote', '--heads', '--tags', repoUrl], {
326
+ timeout: this.gitCheckTimeoutMs,
327
+ stdio: ['ignore', 'pipe', 'pipe'],
328
+ });
329
+
330
+ let output = '';
331
+ let timedOut = false;
332
+ const timeout = setTimeout(() => {
333
+ timedOut = true;
334
+ proc.kill('SIGTERM');
335
+ }, this.gitCheckTimeoutMs);
336
+
337
+ proc.stdout?.on('data', (data) => {
338
+ output += data.toString();
339
+ });
340
+
341
+ proc.on('exit', (code) => {
342
+ clearTimeout(timeout);
343
+ resolve({
344
+ code,
345
+ durationMs: Date.now() - startTime,
346
+ output,
347
+ timedOut,
348
+ });
349
+ });
350
+
351
+ proc.on('error', (error) => {
352
+ clearTimeout(timeout);
353
+ resolve({
354
+ code: null,
355
+ durationMs: Date.now() - startTime,
356
+ output,
357
+ timedOut,
358
+ error,
359
+ });
360
+ });
361
+ });
362
+ }
363
+
364
+ /**
365
+ * Check if git repository is reachable.
366
+ */
367
+ private checkGitReachability(repoUrl: string, result: GitLsRemoteResult): ValidationCheck {
368
+ if (result.timedOut) {
369
+ return {
370
+ name: 'repo-reachable',
371
+ status: 'fail',
372
+ message: `Git repository is not reachable (timeout after ${this.gitCheckTimeoutMs}ms)`,
373
+ detail: repoUrl,
374
+ };
375
+ }
376
+
377
+ if (result.error) {
378
+ return {
379
+ name: 'repo-reachable',
380
+ status: 'fail',
381
+ message: `Failed to check git repository: ${result.error.message}`,
382
+ detail: repoUrl,
383
+ };
384
+ }
385
+
386
+ if (result.code === 0) {
387
+ return {
388
+ name: 'repo-reachable',
389
+ status: 'pass',
390
+ message: `Git repository is reachable (${result.durationMs}ms)`,
391
+ };
392
+ }
393
+
394
+ return {
395
+ name: 'repo-reachable',
396
+ status: 'fail',
397
+ message: 'Git repository is not reachable or invalid URL',
398
+ detail: repoUrl,
399
+ };
400
+ }
401
+
402
+ /**
403
+ * Check if git ref (branch/tag/commit) exists.
404
+ */
405
+ private checkGitRef(repoUrl: string, ref: string, result: GitLsRemoteResult): ValidationCheck {
406
+ if (result.timedOut) {
407
+ return {
408
+ name: 'ref-exists',
409
+ status: 'fail',
410
+ message: `Git ref check timed out after ${this.gitCheckTimeoutMs}ms`,
411
+ detail: `${repoUrl}#${ref}`,
412
+ };
413
+ }
414
+
415
+ if (result.error) {
416
+ return {
417
+ name: 'ref-exists',
418
+ status: 'fail',
419
+ message: `Failed to check git ref: ${result.error.message}`,
420
+ };
421
+ }
422
+
423
+ if (result.code === 0 && this.lsRemoteOutputContainsRef(result.output, ref)) {
424
+ return {
425
+ name: 'ref-exists',
426
+ status: 'pass',
427
+ message: `Git ref '${ref}' exists`,
428
+ };
429
+ }
430
+
431
+ return {
432
+ name: 'ref-exists',
433
+ status: 'fail',
434
+ message: `Git ref '${ref}' does not exist in repository`,
435
+ detail: repoUrl,
436
+ };
437
+ }
438
+
439
+ private lsRemoteOutputContainsRef(output: string, ref: string): boolean {
440
+ const normalizedRef = ref.replace(/^refs\//, '');
441
+ const candidateRefs = new Set([
442
+ ref,
443
+ `refs/${normalizedRef}`,
444
+ `refs/heads/${normalizedRef.replace(/^heads\//, '')}`,
445
+ `refs/tags/${normalizedRef.replace(/^tags\//, '')}`,
446
+ ]);
447
+
448
+ return output
449
+ .split('\n')
450
+ .map((line) => line.trim().split(/\s+/)[1])
451
+ .filter((remoteRef): remoteRef is string => Boolean(remoteRef))
452
+ .some((remoteRef) => candidateRefs.has(remoteRef));
453
+ }
454
+
455
+ /**
456
+ * Check repository size (uses ref count as a rough estimate).
457
+ */
458
+ private checkRepoSize(result: GitLsRemoteResult): ValidationCheck {
459
+ if (result.timedOut) {
460
+ return {
461
+ name: 'repo-size',
462
+ status: 'warning',
463
+ message: 'Could not estimate repository size (check timed out)',
464
+ };
465
+ }
466
+
467
+ if (result.code === 0) {
468
+ // Rough heuristic: number of refs as proxy for size
469
+ const refCount = result.output.split('\n').filter((line) => line.trim()).length;
470
+
471
+ if (refCount > 1000) {
472
+ return {
473
+ name: 'repo-size',
474
+ status: 'warning',
475
+ message: `Repository appears very large (${refCount}+ refs detected); consider using shallow clone`,
476
+ };
477
+ }
478
+
479
+ return {
480
+ name: 'repo-size',
481
+ status: 'pass',
482
+ message: `Repository size is reasonable (${refCount} refs)`,
483
+ };
484
+ }
485
+
486
+ return {
487
+ name: 'repo-size',
488
+ status: 'warning',
489
+ message: 'Could not estimate repository size',
490
+ };
491
+ }
492
+
493
+ /**
494
+ * Validate shell command syntax for validation commands.
495
+ */
496
+ private validateCommandsSyntax(commands: string[]): ValidationCheck {
497
+ const invalid: string[] = [];
498
+
499
+ for (const cmd of commands) {
500
+ // Basic sanity checks: empty, only whitespace, dangerous patterns
501
+ if (!cmd || !cmd.trim()) {
502
+ invalid.push('Empty command');
503
+ continue;
504
+ }
505
+
506
+ // Warn about dangerous patterns (but allow them)
507
+ if (cmd.includes('rm -rf /') || cmd.includes('dd if=/dev')) {
508
+ invalid.push(`Dangerous command: ${cmd.substring(0, 50)}...`);
509
+ }
510
+ }
511
+
512
+ if (invalid.length === 0) {
513
+ return {
514
+ name: 'commands-syntax',
515
+ status: 'pass',
516
+ message: `${commands.length} validation commands syntax is valid`,
517
+ };
518
+ }
519
+
520
+ return {
521
+ name: 'commands-syntax',
522
+ status: 'fail',
523
+ message: `Invalid validation commands: ${invalid.join('; ')}`,
524
+ };
525
+ }
526
+
527
+ /**
528
+ * Validate allowlist patterns (basic regex check).
529
+ */
530
+ private validateAllowlistPatterns(patterns: string[]): ValidationCheck {
531
+ const warnings: string[] = [];
532
+
533
+ for (const pattern of patterns) {
534
+ if (!pattern || !pattern.trim()) {
535
+ warnings.push('Empty pattern');
536
+ continue;
537
+ }
538
+
539
+ // Warn about overly broad patterns
540
+ if (pattern === '*' || pattern === '**' || pattern === '/**') {
541
+ warnings.push(`Pattern '${pattern}' is very broad and may allow too many files`);
542
+ }
543
+
544
+ // Warn about patterns without wildcards (too specific)
545
+ if (!pattern.includes('*') && !pattern.includes('?') && !pattern.includes('[')) {
546
+ // It's OK, exact file paths are allowed
547
+ }
548
+ }
549
+
550
+ if (warnings.length === 0) {
551
+ return {
552
+ name: 'allowlist-patterns',
553
+ status: 'pass',
554
+ message: `${patterns.length} file patterns are valid`,
555
+ };
556
+ }
557
+
558
+ return {
559
+ name: 'allowlist-patterns',
560
+ status: 'warning',
561
+ message: `File allowlist warnings: ${warnings.join('; ')}`,
562
+ };
563
+ }
564
+
565
+ /**
566
+ * Validate maxDiffBytes value.
567
+ */
568
+ private validateDiffBytes(maxDiffBytes?: number): ValidationCheck {
569
+ if (maxDiffBytes === undefined) {
570
+ return {
571
+ name: 'max-diff-bytes',
572
+ status: 'pass',
573
+ message: 'Using default max diff bytes (200 KB)',
574
+ };
575
+ }
576
+
577
+ if (maxDiffBytes < 10000) {
578
+ return {
579
+ name: 'max-diff-bytes',
580
+ status: 'warning',
581
+ message: `Max diff bytes (${maxDiffBytes}) is very small; task may fail due to minimal changes`,
582
+ };
583
+ }
584
+
585
+ if (maxDiffBytes > this.maxRepoSizeBytes) {
586
+ return {
587
+ name: 'max-diff-bytes',
588
+ status: 'warning',
589
+ message: `Max diff bytes (${maxDiffBytes}) exceeds typical repo size; consider reducing`,
590
+ };
591
+ }
592
+
593
+ return {
594
+ name: 'max-diff-bytes',
595
+ status: 'pass',
596
+ message: `Max diff bytes (${maxDiffBytes}) is reasonable`,
597
+ };
598
+ }
599
+
600
+ /**
601
+ * Estimate job duration based on request characteristics.
602
+ */
603
+ private estimateDuration(request: RunRequest): number {
604
+ let estimateSeconds = 180; // Base: 3 minutes
605
+
606
+ // Add for validation commands
607
+ if (request.validationCommands) {
608
+ estimateSeconds += Math.min(request.validationCommands.length * 30, 300);
609
+ }
610
+
611
+ // Add for task prompt length (longer tasks may take longer)
612
+ if (request.taskPrompt) {
613
+ estimateSeconds += Math.min(request.taskPrompt.length / 100, 120);
614
+ }
615
+
616
+ return Math.min(estimateSeconds, 1200); // Cap at 20 minutes
617
+ }
618
+ }
@@ -0,0 +1,35 @@
1
+ import { sanitizeToolName } from './progress-stream-utils.js';
2
+
3
+ describe('sanitizeToolName', () => {
4
+ it('strips XML/HTML tags', () => {
5
+ expect(sanitizeToolName('ls -la</arg_value>')).toBe('ls -la');
6
+ expect(sanitizeToolName('<tool>bash</tool>')).toBe('bash');
7
+ });
8
+
9
+ it('returns "tool" when empty after stripping', () => {
10
+ expect(sanitizeToolName('')).toBe('tool');
11
+ expect(sanitizeToolName(' ')).toBe('tool');
12
+ expect(sanitizeToolName('<foo></foo>')).toBe('tool');
13
+ expect(sanitizeToolName('</arg_value>')).toBe('tool');
14
+ });
15
+
16
+ it('trims whitespace', () => {
17
+ expect(sanitizeToolName(' read_file ')).toBe('read_file');
18
+ });
19
+
20
+ it('collapses whitespace and strips control characters', () => {
21
+ expect(sanitizeToolName('bash\n\t\r -lc\u0000echo hi\u0007')).toBe('bash -lc echo hi');
22
+ });
23
+
24
+ it('truncates to 100 characters', () => {
25
+ expect(sanitizeToolName('a'.repeat(120))).toHaveLength(100);
26
+ expect(sanitizeToolName('a'.repeat(100))).toHaveLength(100);
27
+ expect(sanitizeToolName('a'.repeat(99))).toHaveLength(99);
28
+ });
29
+
30
+ it('preserves valid tool names unchanged', () => {
31
+ expect(sanitizeToolName('read_file')).toBe('read_file');
32
+ expect(sanitizeToolName('src/lib/parser.ts')).toBe('src/lib/parser.ts');
33
+ expect(sanitizeToolName('my-tool.v2')).toBe('my-tool.v2');
34
+ });
35
+ });
@@ -0,0 +1,14 @@
1
+ export const SANITIZE_TOOL_NAME_MAX_LEN = 100;
2
+
3
+ export function sanitizeToolName(raw: string): string {
4
+ const withoutTags = raw.replace(/<[^>]*>/g, ' ');
5
+ const withoutControls = Array.from(withoutTags, (char) => {
6
+ const code = char.charCodeAt(0);
7
+ return (code <= 0x1f || code === 0x7f) ? ' ' : char;
8
+ }).join('');
9
+ const collapsed = withoutControls.replace(/\s+/g, ' ').trim();
10
+ if (!collapsed) return 'tool';
11
+ return collapsed.length > SANITIZE_TOOL_NAME_MAX_LEN
12
+ ? collapsed.slice(0, SANITIZE_TOOL_NAME_MAX_LEN)
13
+ : collapsed;
14
+ }