@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,760 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * kaseki-cli-lib.ts
5
+ *
6
+ * Core library for querying and analyzing kaseki instances.
7
+ * Provides functions for listing instances, reading status, detecting errors,
8
+ * and performing post-run analysis.
9
+ *
10
+ * Usage:
11
+ * import { listInstances, getInstanceStatus } from './kaseki-cli-lib';
12
+ * const instances = listInstances();
13
+ * const status = getInstanceStatus('kaseki-1');
14
+ */
15
+
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import childProcess from 'child_process';
19
+ import {
20
+ readInstanceMetadata,
21
+ Metadata,
22
+ HostStart,
23
+ } from './instance-metadata-reader';
24
+ import {
25
+ deriveInstanceLifecycleStatus,
26
+ resolveInstanceExitCode as importedResolveInstanceExitCode,
27
+ classifyFailure as importedClassifyFailure,
28
+ normalizeExitCodeCandidate as importedNormalizeExitCodeCandidate,
29
+ } from './instance-state-derivation';
30
+
31
+ // ============================================================================
32
+ // Types
33
+ // ============================================================================
34
+
35
+ interface Config {
36
+ KASEKI_RESULTS_DIR: string;
37
+ KASEKI_RUNS_DIR: string;
38
+ }
39
+
40
+ interface KasekiInstance {
41
+ name: string;
42
+ status: 'running' | 'completed' | 'failed' | 'pending';
43
+ running: boolean;
44
+ exitCode: number | null;
45
+ elapsedSeconds: number | null;
46
+ stage: string;
47
+ model: string;
48
+ repo: string;
49
+ ref: string;
50
+ }
51
+
52
+ interface InstanceStatus {
53
+ instance?: string;
54
+ error?: { kind: string; message: string };
55
+ status?: 'running' | 'completed' | 'failed' | 'pending';
56
+ running?: boolean;
57
+ stage?: string;
58
+ elapsedSeconds?: number | null;
59
+ totalDurationSeconds?: number | null;
60
+ agentElapsedSeconds?: number | null;
61
+ timeoutSeconds?: number;
62
+ timeoutRiskPercent?: number;
63
+ timeoutImminent?: boolean;
64
+ timedOut?: boolean;
65
+ exitCode?: number | null;
66
+ failureClass?: string;
67
+ repo?: string;
68
+ ref?: string;
69
+ model?: string;
70
+ }
71
+
72
+ interface ProgressEvent {
73
+ timestamp?: string;
74
+ stage: string;
75
+ message: string;
76
+ malformed?: boolean;
77
+ [key: string]: any;
78
+ }
79
+
80
+ interface DetectedError {
81
+ severity: string;
82
+ source: string;
83
+ line: number;
84
+ message: string;
85
+ }
86
+
87
+ interface AnalysisResult {
88
+ instance?: string;
89
+ error?: { kind: string; message: string };
90
+ status?: string;
91
+ exit_code?: number | string;
92
+ failure_class?: string;
93
+ failed_command?: string;
94
+ validation_failed_command?: string;
95
+ duration_seconds?: number;
96
+ pi_duration_seconds?: number;
97
+ model?: string;
98
+ changed_files_count?: number;
99
+ changed_files?: string[];
100
+ tool_executions?: number;
101
+ errors?: DetectedError[];
102
+ error_count?: number;
103
+ }
104
+
105
+ // Configuration object (can be overridden for testing)
106
+ const config: Config = {
107
+ KASEKI_RESULTS_DIR: process.env.KASEKI_RESULTS_DIR || '/agents/kaseki-results',
108
+ KASEKI_RUNS_DIR: process.env.KASEKI_RUNS_DIR || '/agents/kaseki-runs',
109
+ };
110
+
111
+ // For backwards compatibility, also export as constants
112
+ const KASEKI_RESULTS_DIR = config.KASEKI_RESULTS_DIR;
113
+ const KASEKI_RUNS_DIR = config.KASEKI_RUNS_DIR;
114
+
115
+ // ============================================================================
116
+ // Instance Discovery
117
+ // ============================================================================
118
+
119
+ /**
120
+ * Parse docker ps --format '{{.Names}}' output into container name array.
121
+ */
122
+ function parseDockerContainerNames(dockerNamesOutput: string): string[] {
123
+ if (!dockerNamesOutput) return [];
124
+ return dockerNamesOutput
125
+ .split('\n')
126
+ .map((name) => name.trim())
127
+ .filter((name) => name.length > 0);
128
+ }
129
+
130
+ /**
131
+ * Exact container-name matcher (test seam).
132
+ * Ensures "kaseki-1" does NOT match "kaseki-10".
133
+ */
134
+ function isExactContainerNameMatch(containerName: string, instance: string): boolean {
135
+ return containerName === instance;
136
+ }
137
+
138
+ /**
139
+ * Check whether docker ps names output contains an exact instance match.
140
+ */
141
+ function dockerNamesOutputHasInstance(dockerNamesOutput: string, instance: string): boolean {
142
+ const containerNames = parseDockerContainerNames(dockerNamesOutput);
143
+ return containerNames.some((name) => isExactContainerNameMatch(name, instance));
144
+ }
145
+
146
+ /**
147
+ * Determine if an instance is currently running as a Docker container.
148
+ * Gracefully falls back to false when Docker is unavailable.
149
+ */
150
+ function isInstanceRunning(instance: string): boolean {
151
+ try {
152
+ const dockerNamesOutput = childProcess.execSync('docker ps --format "{{.Names}}" 2>/dev/null || true', {
153
+ encoding: 'utf8',
154
+ });
155
+ return dockerNamesOutputHasInstance(dockerNamesOutput, instance);
156
+ } catch {
157
+ // Docker may not be available
158
+ return false;
159
+ }
160
+ }
161
+
162
+ function isSkippableInstanceIoError(error: any): boolean {
163
+ return error && (error.code === 'ENOENT' || error.code === 'ESTALE');
164
+ }
165
+
166
+ /**
167
+ * Normalize an exit code candidate into an integer or null.
168
+ * (Re-exported from instance-state-derivation for backward compatibility)
169
+ */
170
+ function normalizeExitCodeCandidate(value: any): number | null {
171
+ return importedNormalizeExitCodeCandidate(value);
172
+ }
173
+
174
+ /**
175
+ * Wrapper around resolveInstanceExitCode from instance-state-derivation.
176
+ * @deprecated Use the imported resolveInstanceExitCode directly.
177
+ * Kept for API compatibility; delegates to imported function.
178
+ */
179
+ function resolveInstanceExitCodeLocal(
180
+ resultDir: string,
181
+ metadata: Metadata = {}
182
+ ): number | null {
183
+ return importedResolveInstanceExitCode(resultDir, metadata);
184
+ }
185
+
186
+ /**
187
+ * Wrapper around resolveInstanceStage from instance-state-derivation.
188
+ * Adapts the interface to match existing call sites.
189
+ * Note: The imported function requires resultsDir, but this wrapper uses the config.
190
+ */
191
+ function resolveInstanceStageLocal(
192
+ instance: string,
193
+ metadata: Metadata = {},
194
+ fallback: string = 'unknown'
195
+ ): string {
196
+ if (typeof metadata.current_stage === 'string' && metadata.current_stage.trim().length > 0) {
197
+ return metadata.current_stage;
198
+ }
199
+ const parsedStage = getCurrentStage(instance);
200
+ return parsedStage || fallback;
201
+ }
202
+
203
+ /**
204
+ * Wrapper around classifyFailure from instance-state-derivation.
205
+ * @deprecated Use the imported classifyFailure directly.
206
+ * Kept for API compatibility; delegates to imported function.
207
+ */
208
+ function classifyFailureLocal(
209
+ metadata: Metadata = {},
210
+ exitCode: number | string | null = null
211
+ ): string {
212
+ return importedClassifyFailure(metadata, exitCode);
213
+ }
214
+
215
+ /**
216
+ * List all kaseki instances (running and completed).
217
+ * Returns array of instance objects with basic metadata.
218
+ */
219
+ function listInstances(): KasekiInstance[] {
220
+ const instances: KasekiInstance[] = [];
221
+
222
+ // Scan results directory for completed instances
223
+ let dirs: string[] = [];
224
+ try {
225
+ dirs = fs
226
+ .readdirSync(config.KASEKI_RESULTS_DIR)
227
+ .filter((d) => d.match(/^kaseki-\d+$/) !== null);
228
+ } catch {
229
+ // Results directory may disappear between checks or be transiently unreadable
230
+ dirs = [];
231
+ }
232
+
233
+ for (const dir of dirs) {
234
+ try {
235
+ const instance = dir;
236
+ const resultDir = path.join(config.KASEKI_RESULTS_DIR, instance);
237
+
238
+ // Read metadata and host config
239
+ const { metadata, hostStart, elapsedSeconds } = readInstanceMetadata(resultDir);
240
+
241
+ // Check if currently running via Docker (exact name match)
242
+ const isRunning = isInstanceRunning(instance);
243
+
244
+ // Read exit code from metadata and optional /exit_code file
245
+ const exitCode = importedResolveInstanceExitCode(resultDir, metadata);
246
+
247
+ instances.push({
248
+ name: instance,
249
+ status: deriveInstanceLifecycleStatus(isRunning, exitCode),
250
+ running: isRunning,
251
+ exitCode,
252
+ elapsedSeconds,
253
+ stage: resolveInstanceStageLocal(instance, metadata, 'unknown'),
254
+ model: hostStart.model || metadata.model || 'unknown',
255
+ repo: hostStart.repo_url || hostStart.repo || 'unknown',
256
+ ref: hostStart.git_ref || hostStart.ref || 'unknown',
257
+ });
258
+ } catch (e) {
259
+ if (isSkippableInstanceIoError(e)) {
260
+ // Instance directory can disappear while scanning; skip just this instance.
261
+ continue;
262
+ }
263
+ throw e;
264
+ }
265
+ }
266
+
267
+ return instances.sort((a, b) => {
268
+ // Sort by instance number descending (newest first)
269
+ const aNum = parseInt((a.name.match(/\d+/) || ['0'])[0], 10);
270
+ const bNum = parseInt((b.name.match(/\d+/) || ['0'])[0], 10);
271
+ return bNum - aNum;
272
+ });
273
+ }
274
+
275
+ // ============================================================================
276
+ // Artifact Reading
277
+ // ============================================================================
278
+
279
+ /**
280
+ * Read an artifact file from a kaseki results directory.
281
+ * Returns file contents as string, or null if not found.
282
+ */
283
+ function readArtifact(instance: string, filename: string): string | null {
284
+ const filePath = path.join(config.KASEKI_RESULTS_DIR, instance, filename);
285
+ if (!fs.existsSync(filePath)) {
286
+ return null;
287
+ }
288
+ try {
289
+ return fs.readFileSync(filePath, 'utf8');
290
+ } catch {
291
+ return null;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Read a live-writable log file (tail the last N lines).
297
+ * Useful for stdout.log, stderr.log, validation.log which are continuously written.
298
+ */
299
+ function readLiveLog(instance: string, filename: string, tailLines: number = 50): string | null {
300
+ const content = readArtifact(instance, filename);
301
+ if (content === null) return null;
302
+
303
+ const lines = content.split('\n').filter((line) => line.length > 0);
304
+ return lines.slice(Math.max(0, lines.length - tailLines)).join('\n');
305
+ }
306
+
307
+ /**
308
+ * Read sanitized progress events emitted by the runner.
309
+ */
310
+ function readProgressEvents(instance: string, tailLines: number = 20): ProgressEvent[] | null {
311
+ const content = readArtifact(instance, 'progress.jsonl');
312
+ if (content === null) return null;
313
+
314
+ const lines = content.split('\n').filter((line) => line.trim().length > 0);
315
+ return lines.slice(Math.max(0, lines.length - tailLines)).map((line) => {
316
+ try {
317
+ return JSON.parse(line) as ProgressEvent;
318
+ } catch {
319
+ return {
320
+ timestamp: undefined,
321
+ stage: 'progress',
322
+ message: line,
323
+ malformed: true,
324
+ };
325
+ }
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Parse JSON artifact file.
331
+ * Returns parsed object, or empty object if not found or invalid.
332
+ */
333
+ function readJsonArtifact(instance: string, filename: string): Record<string, any> {
334
+ const content = readArtifact(instance, filename);
335
+ if (!content) return {};
336
+ try {
337
+ return JSON.parse(content);
338
+ } catch {
339
+ return {};
340
+ }
341
+ }
342
+
343
+ function parseTimestampSeconds(value: string | null | undefined): number | null {
344
+ if (!value) return null;
345
+ const parsed = new Date(value).getTime();
346
+ if (Number.isNaN(parsed)) return null;
347
+ return Math.floor(parsed / 1000);
348
+ }
349
+
350
+ function getPiStageStartedAtSeconds(instance: string): number | null {
351
+ const events = readProgressEvents(instance, 500);
352
+ if (!events) return null;
353
+ const started = [...events].reverse().find(
354
+ (event) =>
355
+ event &&
356
+ event.stage === 'pi coding agent' &&
357
+ (event.message === 'started' || (event as any).type === 'agent_start')
358
+ );
359
+ return started ? parseTimestampSeconds(started.timestamp) : null;
360
+ }
361
+
362
+ // ============================================================================
363
+ // Status and Progress
364
+ // ============================================================================
365
+
366
+ /**
367
+ * Get the current stage of a running or completed instance.
368
+ * Parses stdout.log for "==> stage_name" markers.
369
+ */
370
+ function getCurrentStage(instance: string): string {
371
+ const stdout = readArtifact(instance, 'stdout.log');
372
+ if (!stdout) return 'unknown';
373
+
374
+ // Look for the last "==> Stage:" marker
375
+ const matches = stdout.match(/^==> (.+?)$/gm);
376
+ if (!matches || matches.length === 0) return 'unknown';
377
+
378
+ const lastMarker = matches[matches.length - 1];
379
+ return lastMarker.replace(/^==> /, '').trim();
380
+ }
381
+
382
+ /**
383
+ * Get the configured timeout seconds for the instance.
384
+ * Reads from host-start.json or falls back to default (1200).
385
+ */
386
+ function getConfiguredTimeout(instance: string): number {
387
+ const hostStart = readJsonArtifact(instance, 'host-start.json') as HostStart;
388
+ return hostStart.agentTimeoutSeconds ?? 1200;
389
+ }
390
+
391
+ /**
392
+ * Calculate timeout risk percentage (0-100).
393
+ * Returns 0 if no risk, 100 if timed out, or percentage if approaching timeout.
394
+ * Flags warning at 85% of timeout.
395
+ */
396
+ function calculateTimeoutRiskPercent(
397
+ instance: string,
398
+ elapsedSeconds: number | null | undefined
399
+ ): number {
400
+ if (elapsedSeconds === null || elapsedSeconds === undefined) return 0;
401
+
402
+ const timeout = getConfiguredTimeout(instance);
403
+ const percent = (elapsedSeconds / timeout) * 100;
404
+
405
+ return Math.min(Math.max(percent, 0), 100);
406
+ }
407
+
408
+ /**
409
+ * Determine the overall status of an instance.
410
+ * Synthesizes all available state into a unified status object.
411
+ */
412
+ function getInstanceStatus(instance: string): InstanceStatus {
413
+ const resultDir = path.join(config.KASEKI_RESULTS_DIR, instance);
414
+ if (!fs.existsSync(resultDir)) {
415
+ return {
416
+ instance,
417
+ status: 'pending',
418
+ running: false,
419
+ error: {
420
+ kind: 'missing-instance',
421
+ message: `Instance ${instance} not found`,
422
+ },
423
+ };
424
+ }
425
+
426
+ const metadata = readJsonArtifact(instance, 'metadata.json') as Metadata;
427
+ const hostStart = readJsonArtifact(instance, 'host-start.json') as HostStart;
428
+
429
+ // Determine if running
430
+ const isRunning = isInstanceRunning(instance);
431
+
432
+ // Get elapsed time
433
+ let elapsedSeconds: number | null = null;
434
+ if (metadata.duration_seconds !== undefined) {
435
+ elapsedSeconds = metadata.duration_seconds;
436
+ } else if (isRunning) {
437
+ // For running instances, estimate from start timestamp (new key first, legacy fallback)
438
+ const startTimestamp = metadata.started_at || metadata.start_time;
439
+ if (startTimestamp) {
440
+ const startTime = new Date(startTimestamp).getTime();
441
+ if (!Number.isNaN(startTime)) {
442
+ elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
443
+ }
444
+ }
445
+ }
446
+
447
+ // Get stage
448
+ const stage = resolveInstanceStageLocal(instance, metadata, 'unknown');
449
+
450
+ // Get exit code from metadata fallback and /exit_code when available
451
+ const exitCode = importedResolveInstanceExitCode(resultDir, metadata);
452
+
453
+ let agentElapsedSeconds: number | null = null;
454
+ if (metadata.pi_duration_seconds !== undefined) {
455
+ agentElapsedSeconds = metadata.pi_duration_seconds;
456
+ } else if (isRunning && stage === 'pi coding agent') {
457
+ const piStartedAt = getPiStageStartedAtSeconds(instance);
458
+ if (piStartedAt !== null) {
459
+ agentElapsedSeconds = Math.max(Math.floor(Date.now() / 1000) - piStartedAt, 0);
460
+ }
461
+ }
462
+
463
+ const timeoutSeconds = getConfiguredTimeout(instance);
464
+ const timedOut = exitCode === 124;
465
+ const timeoutRiskPercent =
466
+ isRunning && stage === 'pi coding agent'
467
+ ? calculateTimeoutRiskPercent(instance, agentElapsedSeconds)
468
+ : timedOut
469
+ ? 100
470
+ : 0;
471
+ const status = deriveInstanceLifecycleStatus(isRunning, exitCode);
472
+
473
+ return {
474
+ instance,
475
+ status,
476
+ running: isRunning,
477
+ stage,
478
+ elapsedSeconds,
479
+ totalDurationSeconds: elapsedSeconds,
480
+ agentElapsedSeconds,
481
+ timeoutSeconds,
482
+ timeoutRiskPercent,
483
+ timeoutImminent: isRunning && stage === 'pi coding agent' && timeoutRiskPercent >= 85,
484
+ timedOut,
485
+ exitCode,
486
+ failureClass: importedClassifyFailure(metadata, exitCode),
487
+ repo: hostStart.repo_url || hostStart.repo || 'unknown',
488
+ ref: hostStart.git_ref || hostStart.ref || 'unknown',
489
+ model: hostStart.model || 'unknown',
490
+ };
491
+ }
492
+
493
+ // ============================================================================
494
+ // Error Detection
495
+ // ============================================================================
496
+
497
+ /**
498
+ * Error severity levels
499
+ */
500
+ const ErrorSeverity = {
501
+ CRITICAL: 'critical',
502
+ ERROR: 'error',
503
+ WARNING: 'warning',
504
+ };
505
+
506
+ /**
507
+ * Centralized error pattern matchers.
508
+ * Used by detectErrors to identify error patterns in log files.
509
+ */
510
+ const ERROR_PATTERNS = {
511
+ stderr: /error|failed|exception|panic|abort/i,
512
+ stderrExclude: /^#.*error/,
513
+ validation: /FAILED|error|failed/i,
514
+ };
515
+
516
+ /**
517
+ * Scan a log file for lines matching an error pattern.
518
+ * @param instance The kaseki instance name
519
+ * @param artifactName The artifact filename to scan (e.g., 'stderr.log')
520
+ * @param pattern The regex pattern to match against each line
521
+ * @param source The error source label
522
+ * @param severity The error severity level
523
+ * @param excludePattern Optional regex to exclude certain lines
524
+ * @param exitCodeCondition Optional condition to check before scanning (e.g., secret_scan_exit_code !== 0)
525
+ * @returns Array of detected errors
526
+ */
527
+ function scanLogForErrors(
528
+ instance: string,
529
+ artifactName: string,
530
+ pattern: RegExp,
531
+ source: string,
532
+ severity: string,
533
+ options?: {
534
+ excludePattern?: RegExp;
535
+ exitCodeCondition?: boolean;
536
+ allNonEmptyLines?: boolean;
537
+ }
538
+ ): DetectedError[] {
539
+ const errors: DetectedError[] = [];
540
+
541
+ // Check optional exit code condition (e.g., for secret-scan.log)
542
+ if (options?.exitCodeCondition === false) {
543
+ return errors;
544
+ }
545
+
546
+ const log = readArtifact(instance, artifactName);
547
+ if (!log) {
548
+ return errors;
549
+ }
550
+
551
+ const lines = log.split('\n');
552
+ for (let i = 0; i < lines.length; i++) {
553
+ const line = lines[i];
554
+ const lineNumber = i + 1;
555
+
556
+ // Option: treat all non-empty lines as errors (e.g., quality.log)
557
+ if (options?.allNonEmptyLines) {
558
+ if (line.trim().length > 0) {
559
+ errors.push({
560
+ severity,
561
+ source,
562
+ line: lineNumber,
563
+ message: line.substring(0, 150),
564
+ });
565
+ }
566
+ continue;
567
+ }
568
+
569
+ // Standard pattern matching with optional exclusion
570
+ if (line.match(pattern)) {
571
+ if (options?.excludePattern && line.match(options.excludePattern)) {
572
+ continue;
573
+ }
574
+ errors.push({
575
+ severity,
576
+ source,
577
+ line: lineNumber,
578
+ message: line.substring(0, 150),
579
+ });
580
+ }
581
+ }
582
+
583
+ return errors;
584
+ }
585
+
586
+ /**
587
+ * Detect errors in a kaseki instance.
588
+ * Scans stderr, quality gates, secret scans, and validation failures.
589
+ */
590
+ function detectErrors(instance: string): DetectedError[] {
591
+ const errors: DetectedError[] = [];
592
+ const resultDir = path.join(config.KASEKI_RESULTS_DIR, instance);
593
+ if (!fs.existsSync(resultDir)) {
594
+ return errors;
595
+ }
596
+
597
+ const metadata = readJsonArtifact(instance, 'metadata.json') as Metadata;
598
+ const exitCode = importedResolveInstanceExitCode(resultDir, metadata);
599
+
600
+ // Check for empty diff
601
+ if (importedClassifyFailure(metadata, exitCode) === 'empty-diff') {
602
+ errors.push({
603
+ severity: ErrorSeverity.WARNING,
604
+ source: 'empty-diff',
605
+ line: 0,
606
+ message: 'Agent completed without producing a git diff; set KASEKI_TASK_MODE=inspect or KASEKI_ALLOW_EMPTY_DIFF=1 when this is expected.',
607
+ });
608
+ }
609
+
610
+ // Check stderr.log for error patterns
611
+ errors.push(
612
+ ...scanLogForErrors(
613
+ instance,
614
+ 'stderr.log',
615
+ ERROR_PATTERNS.stderr,
616
+ 'stderr',
617
+ ErrorSeverity.ERROR,
618
+ { excludePattern: ERROR_PATTERNS.stderrExclude }
619
+ )
620
+ );
621
+
622
+ // Check quality.log (all non-empty lines are errors)
623
+ errors.push(
624
+ ...scanLogForErrors(
625
+ instance,
626
+ 'quality.log',
627
+ /.*/,
628
+ 'quality-gate',
629
+ ErrorSeverity.CRITICAL,
630
+ { allNonEmptyLines: true }
631
+ )
632
+ );
633
+
634
+ // Check secret-scan.log (conditional on exit code)
635
+ const secretScanExitCode = normalizeExitCodeCandidate(metadata.secret_scan_exit_code);
636
+ errors.push(
637
+ ...scanLogForErrors(
638
+ instance,
639
+ 'secret-scan.log',
640
+ /.*/,
641
+ 'secret-scan',
642
+ ErrorSeverity.CRITICAL,
643
+ { allNonEmptyLines: true, exitCodeCondition: secretScanExitCode !== 0 }
644
+ )
645
+ );
646
+
647
+ // Check validation metadata for failed commands
648
+ if (typeof metadata.validation_failed_command === 'string' && metadata.validation_failed_command.trim().length > 0) {
649
+ errors.push({
650
+ severity: ErrorSeverity.ERROR,
651
+ source: 'validation',
652
+ line: 0,
653
+ message: `Validation failed: ${metadata.validation_failed_command}`.substring(0, 150),
654
+ });
655
+ }
656
+
657
+ // Check validation.log for error patterns
658
+ errors.push(
659
+ ...scanLogForErrors(
660
+ instance,
661
+ 'validation.log',
662
+ ERROR_PATTERNS.validation,
663
+ 'validation',
664
+ ErrorSeverity.ERROR
665
+ )
666
+ );
667
+
668
+ return errors;
669
+ }
670
+
671
+ // ============================================================================
672
+ // Analysis
673
+ // ============================================================================
674
+
675
+ /**
676
+ * Perform comprehensive post-run analysis.
677
+ * Returns aggregate metrics and diagnostics.
678
+ */
679
+ function getAnalysis(instance: string): AnalysisResult {
680
+ const resultDir = path.join(config.KASEKI_RESULTS_DIR, instance);
681
+ if (!fs.existsSync(resultDir)) {
682
+ return {
683
+ instance,
684
+ status: 'failed',
685
+ error: {
686
+ kind: 'missing-instance',
687
+ message: `Instance ${instance} not found`,
688
+ },
689
+ };
690
+ }
691
+
692
+ const metadata = readJsonArtifact(instance, 'metadata.json') as Metadata;
693
+ const piSummary = readJsonArtifact(instance, 'pi-summary.json');
694
+ const changedFilesContent = readArtifact(instance, 'changed-files.txt');
695
+ const changedFiles =
696
+ changedFilesContent?.split('\n').filter(Boolean) || [];
697
+ const errors = detectErrors(instance);
698
+ const resolvedExitCode = importedResolveInstanceExitCode(resultDir, metadata);
699
+ const exitCode = resolvedExitCode !== null ? resolvedExitCode : 'unknown';
700
+ const status = exitCode === 0 ? 'passed' : 'failed';
701
+
702
+ return {
703
+ instance,
704
+ status,
705
+ exit_code: exitCode,
706
+ failure_class: importedClassifyFailure(metadata, resolvedExitCode),
707
+ failed_command: metadata.failed_command || '',
708
+ validation_failed_command: metadata.validation_failed_command || '',
709
+ duration_seconds: metadata.duration_seconds || 0,
710
+ pi_duration_seconds: metadata.pi_duration_seconds || 0,
711
+ model: metadata.model || piSummary.selected_model || 'unknown',
712
+ changed_files_count: changedFiles.length,
713
+ changed_files: changedFiles.slice(0, 10),
714
+ tool_executions: (piSummary.tool_start_count || 0) + (piSummary.tool_end_count || 0),
715
+ errors: errors.slice(0, 10),
716
+ error_count: errors.length,
717
+ };
718
+ }
719
+
720
+ // ============================================================================
721
+ // Exports
722
+ // ============================================================================
723
+
724
+ export {
725
+ config,
726
+ KASEKI_RESULTS_DIR,
727
+ KASEKI_RUNS_DIR,
728
+ listInstances,
729
+ readArtifact,
730
+ readLiveLog,
731
+ readProgressEvents,
732
+ readJsonArtifact,
733
+ getCurrentStage,
734
+ getConfiguredTimeout,
735
+ calculateTimeoutRiskPercent,
736
+ getInstanceStatus,
737
+ detectErrors,
738
+ scanLogForErrors,
739
+ ERROR_PATTERNS,
740
+ getAnalysis,
741
+ ErrorSeverity,
742
+ parseDockerContainerNames,
743
+ isExactContainerNameMatch,
744
+ dockerNamesOutputHasInstance,
745
+ isInstanceRunning,
746
+ deriveInstanceLifecycleStatus,
747
+ normalizeExitCodeCandidate,
748
+ resolveInstanceExitCodeLocal,
749
+ resolveInstanceStageLocal,
750
+ classifyFailureLocal,
751
+ // Types
752
+ type Config,
753
+ type KasekiInstance,
754
+ type Metadata,
755
+ type HostStart,
756
+ type InstanceStatus,
757
+ type ProgressEvent,
758
+ type DetectedError,
759
+ type AnalysisResult,
760
+ };