@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.
- package/.dockerignore +54 -0
- package/.eslintignore +11 -0
- package/.eslintrc.json +95 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +53 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +53 -0
- package/.github/ISSUE_TEMPLATE/security.md +51 -0
- package/.github/PULL_REQUEST_TEMPLATE/default.md +71 -0
- package/.github/dependabot.yml +38 -0
- package/.github/skills/dependency-cache-optimization/SKILL.md +526 -0
- package/.github/skills/docker-image-management/SKILL.md +532 -0
- package/.github/skills/frontend-design/SKILL.md +782 -0
- package/.github/skills/prompt-engineering/SKILL.md +360 -0
- package/.github/skills/quality-gate-config/SKILL.md +591 -0
- package/.github/skills/result-report-analysis/SKILL.md +576 -0
- package/.github/skills/test-automation/SKILL.md +593 -0
- package/.github/skills/workflow-diagnosis/SKILL.md +468 -0
- package/.github/workflows/build-docker-image.yml +453 -0
- package/.github/workflows/release.yml +68 -0
- package/.releaserc.json +135 -0
- package/CHANGELOG.md +117 -0
- package/CLAUDE.md +336 -0
- package/CONTRIBUTING.md +339 -0
- package/Dockerfile +217 -0
- package/README.md +1527 -0
- package/STYLE.md +521 -0
- package/add-js-extensions.d.ts +9 -0
- package/add-js-extensions.d.ts.map +1 -0
- package/add-js-extensions.js.map +1 -0
- package/dist/add-js-extensions.d.ts +9 -0
- package/dist/add-js-extensions.d.ts.map +1 -0
- package/dist/add-js-extensions.js +52 -0
- package/dist/add-js-extensions.js.map +1 -0
- package/dist/ansi-colors.d.ts +26 -0
- package/dist/ansi-colors.d.ts.map +1 -0
- package/dist/ansi-colors.js +51 -0
- package/dist/ansi-colors.js.map +1 -0
- package/dist/cli/BaseCommand.d.ts +18 -0
- package/dist/cli/BaseCommand.d.ts.map +1 -0
- package/dist/cli/BaseCommand.js +31 -0
- package/dist/cli/BaseCommand.js.map +1 -0
- package/dist/cli/KasekiCLI.d.ts +30 -0
- package/dist/cli/KasekiCLI.d.ts.map +1 -0
- package/dist/cli/KasekiCLI.js +134 -0
- package/dist/cli/KasekiCLI.js.map +1 -0
- package/dist/cli/commands/ConfigCommand.d.ts +13 -0
- package/dist/cli/commands/ConfigCommand.d.ts.map +1 -0
- package/dist/cli/commands/ConfigCommand.js +131 -0
- package/dist/cli/commands/ConfigCommand.js.map +1 -0
- package/dist/cli/commands/DoctorCommand.d.ts +45 -0
- package/dist/cli/commands/DoctorCommand.d.ts.map +1 -0
- package/dist/cli/commands/DoctorCommand.js +309 -0
- package/dist/cli/commands/DoctorCommand.js.map +1 -0
- package/dist/cli/commands/ListCommand.d.ts +9 -0
- package/dist/cli/commands/ListCommand.d.ts.map +1 -0
- package/dist/cli/commands/ListCommand.js +81 -0
- package/dist/cli/commands/ListCommand.js.map +1 -0
- package/dist/cli/commands/ReportCommand.d.ts +9 -0
- package/dist/cli/commands/ReportCommand.d.ts.map +1 -0
- package/dist/cli/commands/ReportCommand.js +98 -0
- package/dist/cli/commands/ReportCommand.js.map +1 -0
- package/dist/cli/commands/RunCommand.d.ts +13 -0
- package/dist/cli/commands/RunCommand.d.ts.map +1 -0
- package/dist/cli/commands/RunCommand.js +191 -0
- package/dist/cli/commands/RunCommand.js.map +1 -0
- package/dist/cli/commands/SecretsCommand.d.ts +9 -0
- package/dist/cli/commands/SecretsCommand.d.ts.map +1 -0
- package/dist/cli/commands/SecretsCommand.js +109 -0
- package/dist/cli/commands/SecretsCommand.js.map +1 -0
- package/dist/cli/commands/ServeCommand.d.ts +9 -0
- package/dist/cli/commands/ServeCommand.d.ts.map +1 -0
- package/dist/cli/commands/ServeCommand.js +50 -0
- package/dist/cli/commands/ServeCommand.js.map +1 -0
- package/dist/cli/commands/SetupCommand.d.ts +42 -0
- package/dist/cli/commands/SetupCommand.d.ts.map +1 -0
- package/dist/cli/commands/SetupCommand.js +249 -0
- package/dist/cli/commands/SetupCommand.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +130 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +395 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +446 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/docker/DockerManager.d.ts +69 -0
- package/dist/docker/DockerManager.d.ts.map +1 -0
- package/dist/docker/DockerManager.js +266 -0
- package/dist/docker/DockerManager.js.map +1 -0
- package/dist/event-aggregator.d.ts +71 -0
- package/dist/event-aggregator.d.ts.map +1 -0
- package/dist/event-aggregator.js +95 -0
- package/dist/event-aggregator.js.map +1 -0
- package/dist/github-app-token.d.ts +16 -0
- package/dist/github-app-token.d.ts.map +1 -0
- package/dist/github-app-token.js +148 -0
- package/dist/github-app-token.js.map +1 -0
- package/dist/idempotency-store.d.ts +61 -0
- package/dist/idempotency-store.d.ts.map +1 -0
- package/dist/idempotency-store.js +321 -0
- package/dist/idempotency-store.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/instance/InstanceManager.d.ts +81 -0
- package/dist/instance/InstanceManager.d.ts.map +1 -0
- package/dist/instance/InstanceManager.js +220 -0
- package/dist/instance/InstanceManager.js.map +1 -0
- package/dist/instance-metadata-reader.d.ts +48 -0
- package/dist/instance-metadata-reader.d.ts.map +1 -0
- package/dist/instance-metadata-reader.js +94 -0
- package/dist/instance-metadata-reader.js.map +1 -0
- package/dist/instance-state-derivation.d.ts +42 -0
- package/dist/instance-state-derivation.d.ts.map +1 -0
- package/dist/instance-state-derivation.js +133 -0
- package/dist/instance-state-derivation.js.map +1 -0
- package/dist/job-scheduler.d.ts +124 -0
- package/dist/job-scheduler.d.ts.map +1 -0
- package/dist/job-scheduler.js +992 -0
- package/dist/job-scheduler.js.map +1 -0
- package/dist/kaseki-api-client.d.ts +89 -0
- package/dist/kaseki-api-client.d.ts.map +1 -0
- package/dist/kaseki-api-client.js +405 -0
- package/dist/kaseki-api-client.js.map +1 -0
- package/dist/kaseki-api-config.d.ts +34 -0
- package/dist/kaseki-api-config.d.ts.map +1 -0
- package/dist/kaseki-api-config.js +113 -0
- package/dist/kaseki-api-config.js.map +1 -0
- package/dist/kaseki-api-routes.d.ts +13 -0
- package/dist/kaseki-api-routes.d.ts.map +1 -0
- package/dist/kaseki-api-routes.js +559 -0
- package/dist/kaseki-api-routes.js.map +1 -0
- package/dist/kaseki-api-service-wrapper.d.ts +43 -0
- package/dist/kaseki-api-service-wrapper.d.ts.map +1 -0
- package/dist/kaseki-api-service-wrapper.js +150 -0
- package/dist/kaseki-api-service-wrapper.js.map +1 -0
- package/dist/kaseki-api-service.d.ts +16 -0
- package/dist/kaseki-api-service.d.ts.map +1 -0
- package/dist/kaseki-api-service.js +143 -0
- package/dist/kaseki-api-service.js.map +1 -0
- package/dist/kaseki-api-types.d.ts +440 -0
- package/dist/kaseki-api-types.d.ts.map +1 -0
- package/dist/kaseki-api-types.js +64 -0
- package/dist/kaseki-api-types.js.map +1 -0
- package/dist/kaseki-cli-lib.d.ts +219 -0
- package/dist/kaseki-cli-lib.d.ts.map +1 -0
- package/dist/kaseki-cli-lib.js +523 -0
- package/dist/kaseki-cli-lib.js.map +1 -0
- package/dist/kaseki-cli.d.ts +38 -0
- package/dist/kaseki-cli.d.ts.map +1 -0
- package/dist/kaseki-cli.js +559 -0
- package/dist/kaseki-cli.js.map +1 -0
- package/dist/kaseki-report.d.ts +3 -0
- package/dist/kaseki-report.d.ts.map +1 -0
- package/dist/kaseki-report.js +140 -0
- package/dist/kaseki-report.js.map +1 -0
- package/dist/lib/subprocess-helpers.d.ts +98 -0
- package/dist/lib/subprocess-helpers.d.ts.map +1 -0
- package/dist/lib/subprocess-helpers.js +136 -0
- package/dist/lib/subprocess-helpers.js.map +1 -0
- package/dist/logger.d.ts +39 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +79 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +19 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +59 -0
- package/dist/metrics.js.map +1 -0
- package/dist/middleware/job-lookup.d.ts +27 -0
- package/dist/middleware/job-lookup.d.ts.map +1 -0
- package/dist/middleware/job-lookup.js +28 -0
- package/dist/middleware/job-lookup.js.map +1 -0
- package/dist/pi-event-filter.d.ts +3 -0
- package/dist/pi-event-filter.d.ts.map +1 -0
- package/dist/pi-event-filter.js +126 -0
- package/dist/pi-event-filter.js.map +1 -0
- package/dist/pi-progress-stream.d.ts +3 -0
- package/dist/pi-progress-stream.d.ts.map +1 -0
- package/dist/pi-progress-stream.js +205 -0
- package/dist/pi-progress-stream.js.map +1 -0
- package/dist/pi-progress-summarizer.d.ts +61 -0
- package/dist/pi-progress-summarizer.d.ts.map +1 -0
- package/dist/pi-progress-summarizer.js +246 -0
- package/dist/pi-progress-summarizer.js.map +1 -0
- package/dist/pre-flight-validator.d.ts +72 -0
- package/dist/pre-flight-validator.d.ts.map +1 -0
- package/dist/pre-flight-validator.js +513 -0
- package/dist/pre-flight-validator.js.map +1 -0
- package/dist/progress-stream-utils.d.ts +3 -0
- package/dist/progress-stream-utils.d.ts.map +1 -0
- package/dist/progress-stream-utils.js +15 -0
- package/dist/progress-stream-utils.js.map +1 -0
- package/dist/result-cache.d.ts +52 -0
- package/dist/result-cache.d.ts.map +1 -0
- package/dist/result-cache.js +134 -0
- package/dist/result-cache.js.map +1 -0
- package/dist/routes/artifact-routes.d.ts +10 -0
- package/dist/routes/artifact-routes.d.ts.map +1 -0
- package/dist/routes/artifact-routes.js +126 -0
- package/dist/routes/artifact-routes.js.map +1 -0
- package/dist/routes/log-routes.d.ts +8 -0
- package/dist/routes/log-routes.d.ts.map +1 -0
- package/dist/routes/log-routes.js +345 -0
- package/dist/routes/log-routes.js.map +1 -0
- package/dist/routes/status-routes.d.ts +8 -0
- package/dist/routes/status-routes.d.ts.map +1 -0
- package/dist/routes/status-routes.js +82 -0
- package/dist/routes/status-routes.js.map +1 -0
- package/dist/routes/webhook-routes.d.ts +6 -0
- package/dist/routes/webhook-routes.d.ts.map +1 -0
- package/dist/routes/webhook-routes.js +86 -0
- package/dist/routes/webhook-routes.js.map +1 -0
- package/dist/run-artifact-metadata-cache.d.ts +42 -0
- package/dist/run-artifact-metadata-cache.d.ts.map +1 -0
- package/dist/run-artifact-metadata-cache.js +139 -0
- package/dist/run-artifact-metadata-cache.js.map +1 -0
- package/dist/secret-value-cache.d.ts +13 -0
- package/dist/secret-value-cache.d.ts.map +1 -0
- package/dist/secret-value-cache.js +44 -0
- package/dist/secret-value-cache.js.map +1 -0
- package/dist/secrets/SecretsManager.d.ts +80 -0
- package/dist/secrets/SecretsManager.d.ts.map +1 -0
- package/dist/secrets/SecretsManager.js +306 -0
- package/dist/secrets/SecretsManager.js.map +1 -0
- package/dist/test-utils.d.ts +55 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +48 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/timestamp-tracker.d.ts +75 -0
- package/dist/timestamp-tracker.d.ts.map +1 -0
- package/dist/timestamp-tracker.js +121 -0
- package/dist/timestamp-tracker.js.map +1 -0
- package/dist/utils/failure-artifact-writer.d.ts +29 -0
- package/dist/utils/failure-artifact-writer.d.ts.map +1 -0
- package/dist/utils/failure-artifact-writer.js +157 -0
- package/dist/utils/failure-artifact-writer.js.map +1 -0
- package/dist/utils/file-helpers.d.ts +41 -0
- package/dist/utils/file-helpers.d.ts.map +1 -0
- package/dist/utils/file-helpers.js +143 -0
- package/dist/utils/file-helpers.js.map +1 -0
- package/dist/utils/http-client-factory.d.ts +46 -0
- package/dist/utils/http-client-factory.d.ts.map +1 -0
- package/dist/utils/http-client-factory.js +114 -0
- package/dist/utils/http-client-factory.js.map +1 -0
- package/dist/utils/progress-normalizer.d.ts +13 -0
- package/dist/utils/progress-normalizer.d.ts.map +1 -0
- package/dist/utils/progress-normalizer.js +57 -0
- package/dist/utils/progress-normalizer.js.map +1 -0
- package/dist/utils/response-helpers.d.ts +34 -0
- package/dist/utils/response-helpers.d.ts.map +1 -0
- package/dist/utils/response-helpers.js +78 -0
- package/dist/utils/response-helpers.js.map +1 -0
- package/dist/utils/route-helpers.d.ts +17 -0
- package/dist/utils/route-helpers.d.ts.map +1 -0
- package/dist/utils/route-helpers.js +22 -0
- package/dist/utils/route-helpers.js.map +1 -0
- package/dist/utils/status-response-builder.d.ts +23 -0
- package/dist/utils/status-response-builder.d.ts.map +1 -0
- package/dist/utils/status-response-builder.js +144 -0
- package/dist/utils/status-response-builder.js.map +1 -0
- package/dist/utils/type-guards.d.ts +37 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +45 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/dist/utils/utf8-helpers.d.ts +32 -0
- package/dist/utils/utf8-helpers.d.ts.map +1 -0
- package/dist/utils/utf8-helpers.js +97 -0
- package/dist/utils/utf8-helpers.js.map +1 -0
- package/dist/utils/webhook-event-builder.d.ts +26 -0
- package/dist/utils/webhook-event-builder.d.ts.map +1 -0
- package/dist/utils/webhook-event-builder.js +77 -0
- package/dist/utils/webhook-event-builder.js.map +1 -0
- package/dist/webhook-manager.d.ts +56 -0
- package/dist/webhook-manager.d.ts.map +1 -0
- package/dist/webhook-manager.js +359 -0
- package/dist/webhook-manager.js.map +1 -0
- package/docker/workspace-cache/package-lock.json +13 -0
- package/docker/workspace-cache/package.json +7 -0
- package/docker-compose.yml +53 -0
- package/docs/API.md +708 -0
- package/docs/BACKLOG.md +19 -0
- package/docs/BUILD_STRATEGY.md +404 -0
- package/docs/CLI.md +569 -0
- package/docs/DEPLOYMENT.md +521 -0
- package/docs/DEVELOPMENT.md +459 -0
- package/docs/DOCKER_SETUP.md +522 -0
- package/docs/ENHANCED_PROGRESS_LOGS.md +264 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +549 -0
- package/docs/INTEGRATION_EXAMPLE.md +217 -0
- package/docs/NPM_SETUP.md +468 -0
- package/docs/PHASE1-4_IMPLEMENTATION.md +302 -0
- package/docs/PHASE1_COMPLETION.md +192 -0
- package/docs/PHASE2_COMPLETION.md +134 -0
- package/docs/PHASE6_MIGRATION.md +392 -0
- package/docs/PRINTF_SAFETY_FIX.md +282 -0
- package/docs/QUALITY_GATES.md +369 -0
- package/docs/SETUP_GUIDE.md +482 -0
- package/docs/TASK_PROMPT_TEMPLATES.md +533 -0
- package/docs/VALIDATION_FIX.md +139 -0
- package/docs/VERIFICATION_CHECKLIST.md +335 -0
- package/docs/repo-maturity.md +760 -0
- package/fix-tests.d.ts +9 -0
- package/fix-tests.d.ts.map +1 -0
- package/fix-tests.js.map +1 -0
- package/fix-tests.ts +53 -0
- package/jest.config.ts +31 -0
- package/kaseki +183 -0
- package/kaseki-agent.sh +1961 -0
- package/ops/logrotate/kaseki +10 -0
- package/package.json +83 -0
- package/perf/README.md +54 -0
- package/perf/pi-event-filter.benchmark.test.ts +98 -0
- package/run-kaseki-json.test.sh +106 -0
- package/run-kaseki.sh +990 -0
- package/scripts/allowlist-helper.sh +56 -0
- package/scripts/cleanup-kaseki.sh +168 -0
- package/scripts/deploy-pi-template.sh +293 -0
- package/scripts/docker-entrypoint.sh +71 -0
- package/scripts/dry-run-allowlist.sh +161 -0
- package/scripts/kaseki-activate.sh +396 -0
- package/scripts/kaseki-api.service +62 -0
- package/scripts/kaseki-container-entrypoint-wrapper.sh +119 -0
- package/scripts/kaseki-container-setup-remote.sh +172 -0
- package/scripts/kaseki-container-setup.sh +193 -0
- package/scripts/kaseki-healthcheck.sh +95 -0
- package/scripts/kaseki-install.sh +50 -0
- package/scripts/kaseki-maturity-score.sh +291 -0
- package/scripts/kaseki-performance-metrics.sh +122 -0
- package/scripts/kaseki-preflight.sh +270 -0
- package/scripts/kaseki-setup.sh +265 -0
- package/scripts/pi-setup-remote.sh +213 -0
- package/scripts/setup-github-labels.sh +42 -0
- package/scripts/suggest-allowlist.sh +68 -0
- package/scripts/templates/MULTI_HOST_DISTRIBUTED.md +337 -0
- package/scripts/templates/REST_API_SERVICE.md +490 -0
- package/scripts/templates/SINGLE_HOST_CLI.md +194 -0
- package/scripts/test-github-app.sh +248 -0
- package/src/add-js-extensions.ts +61 -0
- package/src/ansi-colors.test.ts +62 -0
- package/src/ansi-colors.ts +67 -0
- package/src/cli/BaseCommand.ts +40 -0
- package/src/cli/KasekiCLI.ts +154 -0
- package/src/cli/commands/ConfigCommand.ts +145 -0
- package/src/cli/commands/DoctorCommand.ts +329 -0
- package/src/cli/commands/ListCommand.ts +105 -0
- package/src/cli/commands/ReportCommand.ts +110 -0
- package/src/cli/commands/RunCommand.ts +218 -0
- package/src/cli/commands/SecretsCommand.ts +120 -0
- package/src/cli/commands/ServeCommand.ts +62 -0
- package/src/cli/commands/SetupCommand.ts +301 -0
- package/src/cli.ts +138 -0
- package/src/config/ConfigManager.ts +476 -0
- package/src/docker/DockerManager.ts +319 -0
- package/src/docker-entrypoint-packaging.test.ts +33 -0
- package/src/event-aggregator.test.ts +117 -0
- package/src/event-aggregator.ts +126 -0
- package/src/github-app-token.ts +215 -0
- package/src/idempotency-store.test.ts +117 -0
- package/src/idempotency-store.ts +385 -0
- package/src/index.ts +89 -0
- package/src/instance/InstanceManager.ts +285 -0
- package/src/instance-metadata-reader.test.ts +190 -0
- package/src/instance-metadata-reader.ts +129 -0
- package/src/instance-state-derivation.test.ts +263 -0
- package/src/instance-state-derivation.ts +148 -0
- package/src/job-scheduler.test.ts +1236 -0
- package/src/job-scheduler.ts +1117 -0
- package/src/kaseki-api-client.ts +488 -0
- package/src/kaseki-api-config.test.ts +315 -0
- package/src/kaseki-api-config.ts +175 -0
- package/src/kaseki-api-routes.test.ts +1615 -0
- package/src/kaseki-api-routes.ts +643 -0
- package/src/kaseki-api-service-wrapper.ts +188 -0
- package/src/kaseki-api-service.test.ts +418 -0
- package/src/kaseki-api-service.ts +192 -0
- package/src/kaseki-api-types.ts +320 -0
- package/src/kaseki-cli-lib.test.ts +552 -0
- package/src/kaseki-cli-lib.ts +760 -0
- package/src/kaseki-cli.ts +682 -0
- package/src/kaseki-report.test.ts +118 -0
- package/src/kaseki-report.ts +192 -0
- package/src/lib/subprocess-helpers.ts +177 -0
- package/src/logger.ts +114 -0
- package/src/metrics.ts +66 -0
- package/src/middleware/job-lookup.test.ts +113 -0
- package/src/middleware/job-lookup.ts +45 -0
- package/src/pi-event-filter.test.ts +183 -0
- package/src/pi-event-filter.ts +183 -0
- package/src/pi-progress-stream.ts +287 -0
- package/src/pi-progress-summarizer.test.ts +302 -0
- package/src/pi-progress-summarizer.ts +287 -0
- package/src/pre-flight-validator.test.ts +512 -0
- package/src/pre-flight-validator.ts +618 -0
- package/src/progress-stream-utils.test.ts +35 -0
- package/src/progress-stream-utils.ts +14 -0
- package/src/result-cache.test.ts +195 -0
- package/src/result-cache.ts +181 -0
- package/src/routes/artifact-routes.ts +169 -0
- package/src/routes/log-routes.ts +391 -0
- package/src/routes/status-routes.ts +92 -0
- package/src/routes/webhook-routes.ts +97 -0
- package/src/run-artifact-metadata-cache.test.ts +80 -0
- package/src/run-artifact-metadata-cache.ts +184 -0
- package/src/secret-value-cache.test.ts +66 -0
- package/src/secret-value-cache.ts +55 -0
- package/src/secrets/SecretsManager.ts +343 -0
- package/src/test-utils.ts +81 -0
- package/src/timestamp-tracker.test.ts +134 -0
- package/src/timestamp-tracker.ts +132 -0
- package/src/utils/failure-artifact-writer.ts +187 -0
- package/src/utils/file-helpers.test.ts +235 -0
- package/src/utils/file-helpers.ts +150 -0
- package/src/utils/http-client-factory.test.ts +245 -0
- package/src/utils/http-client-factory.ts +157 -0
- package/src/utils/progress-normalizer.test.ts +442 -0
- package/src/utils/progress-normalizer.ts +68 -0
- package/src/utils/response-helpers.test.ts +122 -0
- package/src/utils/response-helpers.ts +101 -0
- package/src/utils/route-helpers.ts +30 -0
- package/src/utils/status-response-builder.ts +159 -0
- package/src/utils/type-guards.ts +52 -0
- package/src/utils/utf8-helpers.ts +102 -0
- package/src/utils/webhook-event-builder.test.ts +143 -0
- package/src/utils/webhook-event-builder.ts +87 -0
- package/src/webhook-manager.test.ts +152 -0
- package/src/webhook-manager.ts +445 -0
- package/templates/allowlist-api-route.txt +7 -0
- package/templates/allowlist-comprehensive.txt +8 -0
- package/templates/allowlist-parser-fix.txt +6 -0
- package/templates/allowlist-ui-component.txt +9 -0
- package/templates/allowlist-utility.txt +9 -0
- package/test/actual-model-metadata.test.sh +102 -0
- package/test/dry-run.test.sh +131 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-0.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-1.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-invalid.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-0.json +1 -0
- package/test/fixtures/kaseki-report-exit-codes/metadata-exit-str-1.json +1 -0
- package/test/kaseki-api.integration.test.sh +165 -0
- package/test/pi-event-filter-failure.test.sh +83 -0
- package/test/printf-safety-focused.test.sh +99 -0
- package/test/printf-safety-results/results/restoration.jsonl +10 -0
- package/test/printf-safety-results/results/test.jsonl +0 -0
- package/test/printf-safety.test.sh +297 -0
- package/test/validation-fix.test.sh +79 -0
- package/test/validation-integration.test.sh +109 -0
- package/tests/allowlist-glob.test.sh +61 -0
- package/tests/dependency-cache-key.test.sh +48 -0
- package/tests/dependency-restore-mode.test.sh +48 -0
- package/tests/doctor-template-parity.test.sh +95 -0
- package/tests/github-operations.test.sh +142 -0
- package/tests/npm-install-flags.test.sh +58 -0
- package/tests/quality-gates.test.sh +178 -0
- package/tests/repo-memory.test.sh +103 -0
- package/tests/restore-disallowed-changes.test.sh +80 -0
- package/tests/validation-missing-npm-scripts.test.sh +93 -0
- package/tests/validation-strict-mode.test.sh +118 -0
- package/tsconfig.changed.json +7 -0
- 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
|
+
};
|