@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,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared generic test utilities and fixtures for unit and integration tests.
|
|
3
|
+
* Consolidates common test setup patterns to reduce duplication.
|
|
4
|
+
*
|
|
5
|
+
* This module contains reusable test factories and types that can be imported
|
|
6
|
+
* by multiple test files. For API-route-specific test setup, see kaseki-api-routes.test.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { KasekiApiConfig } from './kaseki-api-config';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Test-specific config type with jest.fn() mocks.
|
|
13
|
+
*/
|
|
14
|
+
export interface TestScheduler {
|
|
15
|
+
getQueueStatus: jest.Mock;
|
|
16
|
+
getReadiness: jest.Mock;
|
|
17
|
+
getJob: jest.Mock;
|
|
18
|
+
submitJob: jest.Mock;
|
|
19
|
+
listJobs: jest.Mock;
|
|
20
|
+
cancelJob: jest.Mock;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Possible job statuses for mock scheduler.
|
|
25
|
+
*/
|
|
26
|
+
export type MockJobStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Mock job object for tests.
|
|
30
|
+
*/
|
|
31
|
+
export interface MockJob {
|
|
32
|
+
id: string;
|
|
33
|
+
status: MockJobStatus;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
resultDir?: string;
|
|
36
|
+
exitCode?: number;
|
|
37
|
+
failureClass?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a mock scheduler with standard behavior for tests.
|
|
43
|
+
* Customize by providing jobData to override default getJob behavior.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* const scheduler = createMockScheduler();
|
|
47
|
+
* scheduler.getReadiness.mockReturnValue({ ready: false, reasons: ['error'] });
|
|
48
|
+
*/
|
|
49
|
+
export function createMockScheduler(jobData?: { [jobId: string]: MockJob }): TestScheduler {
|
|
50
|
+
return {
|
|
51
|
+
getQueueStatus: jest.fn(() => ({ pending: 0, running: 0, maxConcurrent: 1 })),
|
|
52
|
+
getReadiness: jest.fn(() => ({ ready: true, reasons: [] })),
|
|
53
|
+
getJob: jest.fn((id: string) => jobData?.[id]),
|
|
54
|
+
submitJob: jest.fn(),
|
|
55
|
+
listJobs: jest.fn(() => []),
|
|
56
|
+
cancelJob: jest.fn(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a standard test configuration object.
|
|
62
|
+
* Pass resultsDir to set the directory; other params use reasonable defaults.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const config = createTestConfig('/tmp/results');
|
|
66
|
+
*/
|
|
67
|
+
export function createTestConfig(resultsDir: string): KasekiApiConfig {
|
|
68
|
+
return {
|
|
69
|
+
port: 0,
|
|
70
|
+
apiKeys: ['test-key'],
|
|
71
|
+
resultsDir,
|
|
72
|
+
maxConcurrentRuns: 1,
|
|
73
|
+
defaultTaskMode: 'patch' as const,
|
|
74
|
+
maxDiffBytes: 200000,
|
|
75
|
+
agentTimeoutSeconds: 1200,
|
|
76
|
+
logLevel: 'info' as const,
|
|
77
|
+
artifactCacheMaxEntries: 20,
|
|
78
|
+
artifactCacheTtlMs: 5 * 60 * 1000,
|
|
79
|
+
artifactCacheMaxFileBytes: 10 * 1024 * 1024,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { TimestampTracker } from './timestamp-tracker';
|
|
2
|
+
|
|
3
|
+
describe('TimestampTracker', () => {
|
|
4
|
+
it('should record and retrieve first and last ISO timestamps', () => {
|
|
5
|
+
const tracker = new TimestampTracker();
|
|
6
|
+
tracker.record('2024-01-01T10:00:00Z');
|
|
7
|
+
tracker.record('2024-01-01T10:05:00Z');
|
|
8
|
+
tracker.record('2024-01-01T10:10:00Z');
|
|
9
|
+
|
|
10
|
+
expect(tracker.firstTimestamp()).toBe('2024-01-01T10:00:00Z');
|
|
11
|
+
expect(tracker.lastTimestamp()).toBe('2024-01-01T10:10:00Z');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return null for first/last when no timestamps recorded', () => {
|
|
15
|
+
const tracker = new TimestampTracker();
|
|
16
|
+
expect(tracker.firstTimestamp()).toBeNull();
|
|
17
|
+
expect(tracker.lastTimestamp()).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should ignore null and empty timestamps', () => {
|
|
21
|
+
const tracker = new TimestampTracker();
|
|
22
|
+
tracker.record(null);
|
|
23
|
+
tracker.record('');
|
|
24
|
+
|
|
25
|
+
expect(tracker.firstTimestamp()).toBeNull();
|
|
26
|
+
expect(tracker.lastTimestamp()).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should convert ISO timestamps to epoch milliseconds', () => {
|
|
30
|
+
const tracker = new TimestampTracker();
|
|
31
|
+
const iso = '2024-01-01T00:00:00Z';
|
|
32
|
+
tracker.record(iso);
|
|
33
|
+
|
|
34
|
+
const epochMs = tracker.getEpochMs(iso);
|
|
35
|
+
expect(epochMs).toBe(1704067200000);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return null for malformed timestamps', () => {
|
|
39
|
+
const tracker = new TimestampTracker();
|
|
40
|
+
const malformed = 'not-a-timestamp';
|
|
41
|
+
|
|
42
|
+
const epochMs = tracker.getEpochMs(malformed);
|
|
43
|
+
expect(epochMs).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle malformed timestamps gracefully during record', () => {
|
|
47
|
+
const tracker = new TimestampTracker();
|
|
48
|
+
tracker.record('2024-01-01T10:00:00Z');
|
|
49
|
+
tracker.record('invalid-timestamp');
|
|
50
|
+
tracker.record('2024-01-01T10:05:00Z');
|
|
51
|
+
|
|
52
|
+
// Should still track valid timestamps
|
|
53
|
+
expect(tracker.firstTimestamp()).toBe('2024-01-01T10:00:00Z');
|
|
54
|
+
expect(tracker.lastTimestamp()).toBe('2024-01-01T10:05:00Z');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should track min and max epoch milliseconds', () => {
|
|
58
|
+
const tracker = new TimestampTracker();
|
|
59
|
+
tracker.record('2024-01-01T10:05:00Z');
|
|
60
|
+
tracker.record('2024-01-01T10:00:00Z');
|
|
61
|
+
tracker.record('2024-01-01T10:10:00Z');
|
|
62
|
+
|
|
63
|
+
const firstEpochMs = tracker.firstEpochMs();
|
|
64
|
+
const lastEpochMs = tracker.lastEpochMs();
|
|
65
|
+
|
|
66
|
+
// Epoch millis should be min and max across all recorded timestamps
|
|
67
|
+
const expectedMin = new Date('2024-01-01T10:00:00Z').getTime();
|
|
68
|
+
const expectedMax = new Date('2024-01-01T10:10:00Z').getTime();
|
|
69
|
+
|
|
70
|
+
expect(firstEpochMs).toBe(expectedMin);
|
|
71
|
+
expect(lastEpochMs).toBe(expectedMax);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should calculate duration in milliseconds between first and last epochs', () => {
|
|
75
|
+
const tracker = new TimestampTracker();
|
|
76
|
+
tracker.record('2024-01-01T10:00:00Z');
|
|
77
|
+
tracker.record('2024-01-01T10:05:00Z');
|
|
78
|
+
|
|
79
|
+
const duration = tracker.durationMs();
|
|
80
|
+
expect(duration).toBe(5 * 60 * 1000); // 5 minutes in ms
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should return null for duration when timestamps are missing', () => {
|
|
84
|
+
const tracker = new TimestampTracker();
|
|
85
|
+
expect(tracker.durationMs()).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should return 0 for duration when only one timestamp is recorded', () => {
|
|
89
|
+
const tracker = new TimestampTracker();
|
|
90
|
+
tracker.record('2024-01-01T10:00:00Z');
|
|
91
|
+
// After recording one timestamp, both first and last are the same
|
|
92
|
+
const duration = tracker.durationMs();
|
|
93
|
+
expect(duration).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should return 0 for duration when first and last are the same', () => {
|
|
97
|
+
const tracker = new TimestampTracker();
|
|
98
|
+
const timestamp = '2024-01-01T10:00:00Z';
|
|
99
|
+
tracker.record(timestamp);
|
|
100
|
+
tracker.record(timestamp);
|
|
101
|
+
|
|
102
|
+
const duration = tracker.durationMs();
|
|
103
|
+
expect(duration).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should provide complete range information', () => {
|
|
107
|
+
const tracker = new TimestampTracker();
|
|
108
|
+
tracker.record('2024-01-01T10:00:00Z');
|
|
109
|
+
tracker.record('2024-01-01T10:05:00Z');
|
|
110
|
+
|
|
111
|
+
const range = tracker.range();
|
|
112
|
+
expect(range.first).toBe('2024-01-01T10:00:00Z');
|
|
113
|
+
expect(range.last).toBe('2024-01-01T10:05:00Z');
|
|
114
|
+
expect(range.minEpochMs).toBe(new Date('2024-01-01T10:00:00Z').getTime());
|
|
115
|
+
expect(range.maxEpochMs).toBe(new Date('2024-01-01T10:05:00Z').getTime());
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle millisecond precision in timestamps', () => {
|
|
119
|
+
const tracker = new TimestampTracker();
|
|
120
|
+
tracker.record('2024-01-01T10:00:00.123Z');
|
|
121
|
+
tracker.record('2024-01-01T10:00:00.456Z');
|
|
122
|
+
|
|
123
|
+
const duration = tracker.durationMs();
|
|
124
|
+
expect(duration).toBe(333); // 456 - 123 = 333 ms
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle numeric epoch timestamps (if converted to ISO)', () => {
|
|
128
|
+
const tracker = new TimestampTracker();
|
|
129
|
+
const isoString = new Date(1704067200000).toISOString();
|
|
130
|
+
tracker.record(isoString);
|
|
131
|
+
|
|
132
|
+
expect(tracker.getEpochMs(isoString)).toBe(1704067200000);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* timestamp-tracker.ts
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates timestamp tracking logic for Pi event stream processing.
|
|
5
|
+
* Safely parses ISO timestamps, tracks min/max values, and converts to epoch milliseconds.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface TimestampRange {
|
|
9
|
+
first: string | null;
|
|
10
|
+
last: string | null;
|
|
11
|
+
minEpochMs: number | null;
|
|
12
|
+
maxEpochMs: number | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* TimestampTracker manages timestamp observations from an event stream.
|
|
17
|
+
*
|
|
18
|
+
* Responsibilities:
|
|
19
|
+
* - Parse and record ISO 8601 timestamps
|
|
20
|
+
* - Track first and last observed timestamps (as ISO strings)
|
|
21
|
+
* - Track min/max epoch milliseconds for duration calculation
|
|
22
|
+
* - Provide safe conversion from ISO string to epoch milliseconds
|
|
23
|
+
* - Handle malformed timestamps gracefully
|
|
24
|
+
*/
|
|
25
|
+
export class TimestampTracker {
|
|
26
|
+
private first: string | null = null;
|
|
27
|
+
private last: string | null = null;
|
|
28
|
+
private minEpochMs: number | null = null;
|
|
29
|
+
private maxEpochMs: number | null = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert ISO 8601 string to epoch milliseconds.
|
|
33
|
+
* Returns null if the string cannot be parsed.
|
|
34
|
+
*/
|
|
35
|
+
private parseToEpochMs(timestamp: string | null): number | null {
|
|
36
|
+
if (!timestamp) return null;
|
|
37
|
+
const epochMs = Date.parse(timestamp);
|
|
38
|
+
return Number.isFinite(epochMs) ? epochMs : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Record an ISO timestamp observation.
|
|
43
|
+
* Safely handles malformed timestamps by ignoring them.
|
|
44
|
+
*/
|
|
45
|
+
record(timestamp: string | null): void {
|
|
46
|
+
if (!timestamp) return;
|
|
47
|
+
|
|
48
|
+
// Track first and last as ISO strings
|
|
49
|
+
if (this.first === null) {
|
|
50
|
+
this.first = timestamp;
|
|
51
|
+
}
|
|
52
|
+
this.last = timestamp;
|
|
53
|
+
|
|
54
|
+
// Track min/max as epoch milliseconds for duration calculation
|
|
55
|
+
const epochMs = this.parseToEpochMs(timestamp);
|
|
56
|
+
if (epochMs !== null) {
|
|
57
|
+
this.minEpochMs =
|
|
58
|
+
this.minEpochMs === null ? epochMs : Math.min(this.minEpochMs, epochMs);
|
|
59
|
+
this.maxEpochMs =
|
|
60
|
+
this.maxEpochMs === null ? epochMs : Math.max(this.maxEpochMs, epochMs);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get epoch milliseconds for a given ISO timestamp string.
|
|
66
|
+
* Useful for extracting individual timestamp conversions.
|
|
67
|
+
*/
|
|
68
|
+
getEpochMs(timestamp: string | null): number | null {
|
|
69
|
+
return this.parseToEpochMs(timestamp);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the first recorded timestamp (ISO string).
|
|
74
|
+
* Returns null if no timestamps have been recorded.
|
|
75
|
+
*/
|
|
76
|
+
firstTimestamp(): string | null {
|
|
77
|
+
return this.first;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the last recorded timestamp (ISO string).
|
|
82
|
+
* Returns null if no timestamps have been recorded.
|
|
83
|
+
*/
|
|
84
|
+
lastTimestamp(): string | null {
|
|
85
|
+
return this.last;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the first timestamp as epoch milliseconds.
|
|
90
|
+
* If first timestamp string exists but is malformed, attempts parse; otherwise
|
|
91
|
+
* uses tracked minEpochMs (which may be different if timestamps came from different sources).
|
|
92
|
+
*/
|
|
93
|
+
firstEpochMs(): number | null {
|
|
94
|
+
if (this.minEpochMs !== null) return this.minEpochMs;
|
|
95
|
+
if (this.first !== null) return this.parseToEpochMs(this.first);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get the last timestamp as epoch milliseconds.
|
|
101
|
+
* If last timestamp string exists but is malformed, attempts parse; otherwise
|
|
102
|
+
* uses tracked maxEpochMs (which may be different if timestamps came from different sources).
|
|
103
|
+
*/
|
|
104
|
+
lastEpochMs(): number | null {
|
|
105
|
+
if (this.maxEpochMs !== null) return this.maxEpochMs;
|
|
106
|
+
if (this.last !== null) return this.parseToEpochMs(this.last);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Calculate duration in milliseconds between first and last recorded epochs.
|
|
112
|
+
* Returns null if either bound is missing or invalid.
|
|
113
|
+
*/
|
|
114
|
+
durationMs(): number | null {
|
|
115
|
+
const first = this.firstEpochMs();
|
|
116
|
+
const last = this.lastEpochMs();
|
|
117
|
+
if (first === null || last === null) return null;
|
|
118
|
+
return Math.max(0, last - first);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get complete timestamp range information.
|
|
123
|
+
*/
|
|
124
|
+
range(): TimestampRange {
|
|
125
|
+
return {
|
|
126
|
+
first: this.first,
|
|
127
|
+
last: this.last,
|
|
128
|
+
minEpochMs: this.minEpochMs,
|
|
129
|
+
maxEpochMs: this.maxEpochMs,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
4
|
+
import { Job } from '../kaseki-api-types';
|
|
5
|
+
|
|
6
|
+
export type CleanupResult = {
|
|
7
|
+
attempted: boolean;
|
|
8
|
+
ok?: boolean;
|
|
9
|
+
detail?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handles writing failure artifacts (failure.json, analysis.md, result-summary.md, etc.)
|
|
14
|
+
* when a job fails before complete diagnostics are available.
|
|
15
|
+
*/
|
|
16
|
+
export class FailureArtifactWriter {
|
|
17
|
+
constructor(private resultsDir: string) {}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Write API finalization artifacts when job fails before container diagnostics.
|
|
21
|
+
*/
|
|
22
|
+
writeFailureArtifacts(
|
|
23
|
+
job: Job,
|
|
24
|
+
cleanup: CleanupResult,
|
|
25
|
+
options?: { stdoutTail?: Buffer<ArrayBufferLike>; stderrTail?: Buffer<ArrayBufferLike>; lastStage?: string }
|
|
26
|
+
): void {
|
|
27
|
+
if (job.status !== 'failed' && !job.failureClass) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const resultDir = path.join(this.resultsDir, job.id);
|
|
32
|
+
const now = (job.completedAt || new Date()).toISOString();
|
|
33
|
+
try {
|
|
34
|
+
fs.mkdirSync(resultDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
this.writeFailureJson(resultDir, job, now, cleanup);
|
|
37
|
+
this.writeResultSummary(resultDir, job, now);
|
|
38
|
+
this.writeAnalysis(resultDir, job, now, cleanup, options?.lastStage);
|
|
39
|
+
this.writeMetadata(resultDir, job, now);
|
|
40
|
+
this.writeStderrLog(resultDir, job, options?.stdoutTail, options?.stderrTail);
|
|
41
|
+
} catch {
|
|
42
|
+
// Best effort diagnostics; never mask the primary job failure.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private writeFailureJson(resultDir: string, job: Job, now: string, cleanup: CleanupResult): void {
|
|
47
|
+
const failurePath = path.join(resultDir, 'failure.json');
|
|
48
|
+
const shouldWriteFailure = !fs.existsSync(failurePath) || fs.statSync(failurePath).size === 0;
|
|
49
|
+
if (shouldWriteFailure) {
|
|
50
|
+
const payload = {
|
|
51
|
+
failureClass: job.failureClass || 'api_finalized',
|
|
52
|
+
error: job.error || 'Job failed before runner failure metadata was written',
|
|
53
|
+
exitCode: job.exitCode,
|
|
54
|
+
cancelledAt: job.failureClass === 'cancelled' ? now : undefined,
|
|
55
|
+
completedAt: now,
|
|
56
|
+
apiFinalized: true,
|
|
57
|
+
cleanup,
|
|
58
|
+
};
|
|
59
|
+
fs.writeFileSync(failurePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private writeResultSummary(resultDir: string, job: Job, now: string): void {
|
|
64
|
+
const summaryPath = path.join(resultDir, 'result-summary.md');
|
|
65
|
+
const shouldWriteSummary = !fs.existsSync(summaryPath) || fs.statSync(summaryPath).size === 0;
|
|
66
|
+
if (shouldWriteSummary) {
|
|
67
|
+
fs.writeFileSync(
|
|
68
|
+
summaryPath,
|
|
69
|
+
[
|
|
70
|
+
`# ${job.id} failed`,
|
|
71
|
+
'',
|
|
72
|
+
`Failure class: ${job.failureClass || 'unknown'}`,
|
|
73
|
+
`Exit code: ${job.exitCode ?? 'unknown'}`,
|
|
74
|
+
`Error: ${job.error || 'unknown'}`,
|
|
75
|
+
`Completed at: ${now}`,
|
|
76
|
+
'',
|
|
77
|
+
].join('\n'),
|
|
78
|
+
'utf-8'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private writeAnalysis(resultDir: string, job: Job, now: string, cleanup: CleanupResult, lastStage?: string): void {
|
|
84
|
+
const analysisPath = path.join(resultDir, 'analysis.md');
|
|
85
|
+
const shouldWriteAnalysis = !fs.existsSync(analysisPath) || fs.statSync(analysisPath).size === 0;
|
|
86
|
+
if (shouldWriteAnalysis) {
|
|
87
|
+
fs.writeFileSync(
|
|
88
|
+
analysisPath,
|
|
89
|
+
[
|
|
90
|
+
`# Failure analysis for ${job.id}`,
|
|
91
|
+
'',
|
|
92
|
+
'## Completed work',
|
|
93
|
+
`- Job lifecycle entered: ${job.startedAt ? 'running' : 'queued'}`,
|
|
94
|
+
'- API finalization fallback written: yes',
|
|
95
|
+
'',
|
|
96
|
+
'## Failure classification',
|
|
97
|
+
`- Failure class: ${job.failureClass || 'unknown'}`,
|
|
98
|
+
`- Exit code: ${job.exitCode ?? 'unknown'}`,
|
|
99
|
+
`- Error: ${job.error || 'unknown'}`,
|
|
100
|
+
`- Last stage: ${lastStage || 'unknown'}`,
|
|
101
|
+
'',
|
|
102
|
+
'## Known warnings',
|
|
103
|
+
`- Container cleanup attempted: ${cleanup.attempted ? 'yes' : 'no'}`,
|
|
104
|
+
`- Container cleanup ok: ${cleanup.ok ? 'yes' : 'no'}`,
|
|
105
|
+
`- Cleanup detail: ${cleanup.detail || 'none'}`,
|
|
106
|
+
'',
|
|
107
|
+
`Completed at: ${now}`,
|
|
108
|
+
'',
|
|
109
|
+
].join('\n'),
|
|
110
|
+
'utf-8'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private writeMetadata(resultDir: string, job: Job, now: string): void {
|
|
116
|
+
const metadataPath = path.join(resultDir, 'metadata.json');
|
|
117
|
+
const shouldWriteMetadata = !fs.existsSync(metadataPath) || fs.statSync(metadataPath).size === 0;
|
|
118
|
+
if (shouldWriteMetadata) {
|
|
119
|
+
const startedAt = job.startedAt?.toISOString();
|
|
120
|
+
const completedAt = job.completedAt?.toISOString() || now;
|
|
121
|
+
const durationSeconds =
|
|
122
|
+
job.startedAt && (job.completedAt || now)
|
|
123
|
+
? Math.max(0, Math.round((new Date(completedAt).getTime() - job.startedAt.getTime()) / 1000))
|
|
124
|
+
: undefined;
|
|
125
|
+
const payload = {
|
|
126
|
+
id: job.id,
|
|
127
|
+
status: job.status,
|
|
128
|
+
timestamps: {
|
|
129
|
+
createdAt: job.createdAt.toISOString(),
|
|
130
|
+
startedAt,
|
|
131
|
+
completedAt,
|
|
132
|
+
},
|
|
133
|
+
durations: {
|
|
134
|
+
totalSeconds: durationSeconds,
|
|
135
|
+
},
|
|
136
|
+
runtime: {
|
|
137
|
+
timeoutSeconds: job.effectiveTimeoutSeconds,
|
|
138
|
+
pid: job.processId,
|
|
139
|
+
nodeVersion: process.version,
|
|
140
|
+
platform: process.platform,
|
|
141
|
+
},
|
|
142
|
+
env: {
|
|
143
|
+
taskMode: job.request.taskMode,
|
|
144
|
+
startupCheck: !!job.request.startupCheck,
|
|
145
|
+
},
|
|
146
|
+
failure: {
|
|
147
|
+
failureClass: job.failureClass || 'api_finalized',
|
|
148
|
+
error: job.error || 'Job failed before runner failure metadata was written',
|
|
149
|
+
exitCode: job.exitCode,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
fs.writeFileSync(metadataPath, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private writeStderrLog(
|
|
157
|
+
resultDir: string,
|
|
158
|
+
job: Job,
|
|
159
|
+
stdoutTail?: Buffer<ArrayBufferLike>,
|
|
160
|
+
stderrTail?: Buffer<ArrayBufferLike>
|
|
161
|
+
): void {
|
|
162
|
+
const stderrPath = path.join(resultDir, 'stderr.log');
|
|
163
|
+
const shouldWriteStderr = !fs.existsSync(stderrPath) || fs.statSync(stderrPath).size === 0;
|
|
164
|
+
if (shouldWriteStderr) {
|
|
165
|
+
const decodedStderr = stderrTail ? this.decodeUtf8Tail(stderrTail) : '';
|
|
166
|
+
const decodedStdout = stdoutTail ? this.decodeUtf8Tail(stdoutTail) : '';
|
|
167
|
+
const content = [
|
|
168
|
+
'stderr fallback generated by API finalization',
|
|
169
|
+
`failureClass=${job.failureClass || 'unknown'} exitCode=${job.exitCode ?? 'unknown'}`,
|
|
170
|
+
`error=${job.error || 'unknown'}`,
|
|
171
|
+
'',
|
|
172
|
+
decodedStderr ? '--- captured stderr tail ---' : '',
|
|
173
|
+
decodedStderr,
|
|
174
|
+
decodedStdout ? '--- captured stdout tail ---' : '',
|
|
175
|
+
decodedStdout,
|
|
176
|
+
]
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.join('\n');
|
|
179
|
+
fs.writeFileSync(stderrPath, `${content}\n`, 'utf-8');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private decodeUtf8Tail(tail: Buffer<ArrayBufferLike>): string {
|
|
184
|
+
const decoder = new StringDecoder('utf8');
|
|
185
|
+
return decoder.end(tail);
|
|
186
|
+
}
|
|
187
|
+
}
|