@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,118 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
describe('kaseki-report', () => {
|
|
7
|
+
let baseDir: string;
|
|
8
|
+
let suiteStartNs: bigint;
|
|
9
|
+
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
suiteStartNs = process.hrtime.bigint();
|
|
12
|
+
baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kaseki-report-test-'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
const elapsedMs = Number(process.hrtime.bigint() - suiteStartNs) / 1_000_000;
|
|
17
|
+
process.stdout.write(`\n[kaseki-report.test] runtime_ms=${elapsedMs.toFixed(2)}\n`);
|
|
18
|
+
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function createFixture(
|
|
22
|
+
name: string,
|
|
23
|
+
exitCodeValue: any,
|
|
24
|
+
validationFailedCommand = ''
|
|
25
|
+
): { fixtureDir: string; metadataPath: string } {
|
|
26
|
+
const dir = path.join(baseDir, name);
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
fs.writeFileSync(
|
|
29
|
+
path.join(dir, 'metadata.json'),
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
instance: name,
|
|
32
|
+
exit_code: exitCodeValue,
|
|
33
|
+
pi_exit_code: exitCodeValue,
|
|
34
|
+
validation_exit_code: exitCodeValue,
|
|
35
|
+
validation_failed_command: validationFailedCommand,
|
|
36
|
+
quality_exit_code: exitCodeValue,
|
|
37
|
+
secret_scan_exit_code: exitCodeValue,
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
fs.writeFileSync(path.join(dir, 'changed-files.txt'), 'src/index.js\n');
|
|
41
|
+
return { fixtureDir: dir, metadataPath: path.join(dir, 'metadata.json') };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function runFixture(fixtureDir: string): {
|
|
45
|
+
stdout: string;
|
|
46
|
+
stderr: string;
|
|
47
|
+
code: number | null;
|
|
48
|
+
} {
|
|
49
|
+
const result = spawnSync('npx', ['tsx', 'src/kaseki-report.ts', fixtureDir], {
|
|
50
|
+
cwd: path.join(__dirname, '..'),
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
});
|
|
53
|
+
return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const testCases = [
|
|
57
|
+
{
|
|
58
|
+
name: 'exit-num-zero',
|
|
59
|
+
exitCodeValue: 0,
|
|
60
|
+
expectedStatus: 'passed',
|
|
61
|
+
expectedCode: '0',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'exit-str-zero',
|
|
65
|
+
exitCodeValue: '0',
|
|
66
|
+
expectedStatus: 'passed',
|
|
67
|
+
expectedCode: '0',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'exit-num-one',
|
|
71
|
+
exitCodeValue: 1,
|
|
72
|
+
expectedStatus: 'failed',
|
|
73
|
+
expectedCode: '1',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'exit-str-one',
|
|
77
|
+
exitCodeValue: '1',
|
|
78
|
+
expectedStatus: 'failed',
|
|
79
|
+
expectedCode: '1',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'exit-invalid',
|
|
83
|
+
exitCodeValue: 'invalid',
|
|
84
|
+
expectedStatus: 'failed',
|
|
85
|
+
expectedCode: 'unknown',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
testCases.forEach(({ name, exitCodeValue, expectedStatus, expectedCode }) => {
|
|
90
|
+
test(`${name}: normalizes exit code and reflects status`, () => {
|
|
91
|
+
const { fixtureDir } = createFixture(name, exitCodeValue);
|
|
92
|
+
const result = runFixture(fixtureDir);
|
|
93
|
+
|
|
94
|
+
expect(result.code).toBe(0);
|
|
95
|
+
const expectedLines = [
|
|
96
|
+
`Status: ${expectedStatus}`,
|
|
97
|
+
`Exit code: ${expectedCode}`,
|
|
98
|
+
`Pi exit code: ${expectedCode}`,
|
|
99
|
+
];
|
|
100
|
+
expectedLines.forEach((line) => expect(result.stdout).toContain(line));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('prints validation failed command detail when metadata includes it', () => {
|
|
105
|
+
const { fixtureDir } = createFixture(
|
|
106
|
+
'validation-detail',
|
|
107
|
+
1,
|
|
108
|
+
'first failing command was "npm run check" with exit 1'
|
|
109
|
+
);
|
|
110
|
+
const result = runFixture(fixtureDir);
|
|
111
|
+
|
|
112
|
+
expect(result.code).toBe(0);
|
|
113
|
+
expect(result.stdout).toContain(
|
|
114
|
+
'Validation failed command: first failing command was "npm run check" with exit 1'
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { normalizeExitCodeCandidate } from './instance-state-derivation';
|
|
5
|
+
|
|
6
|
+
const resultDir = process.argv[2] ?? '/results';
|
|
7
|
+
|
|
8
|
+
interface Metadata {
|
|
9
|
+
instance?: string;
|
|
10
|
+
exit_code?: number | string;
|
|
11
|
+
failed_command?: string;
|
|
12
|
+
pi_exit_code?: number | string;
|
|
13
|
+
validation_exit_code?: number | string;
|
|
14
|
+
validation_failed_command?: string;
|
|
15
|
+
validation_fail_fast_mode?: boolean;
|
|
16
|
+
validation_stopped_early?: boolean;
|
|
17
|
+
validation_commands_attempted?: number;
|
|
18
|
+
quality_exit_code?: number | string;
|
|
19
|
+
secret_scan_exit_code?: number | string;
|
|
20
|
+
model?: string;
|
|
21
|
+
actual_model?: string;
|
|
22
|
+
pi_version?: string;
|
|
23
|
+
duration_seconds?: number;
|
|
24
|
+
pi_duration_seconds?: number;
|
|
25
|
+
diff_nonempty?: boolean;
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PiSummary {
|
|
30
|
+
selected_model?: string;
|
|
31
|
+
[key: string]: any;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface FileContent {
|
|
35
|
+
name: string;
|
|
36
|
+
text: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readText(name: string): string {
|
|
40
|
+
try {
|
|
41
|
+
return fs.readFileSync(path.join(resultDir, name), 'utf8');
|
|
42
|
+
} catch {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readJson(name: string): Record<string, any> {
|
|
48
|
+
const text = readText(name);
|
|
49
|
+
if (!text.trim()) return {};
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(text);
|
|
52
|
+
} catch {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function firstReadableNonEmpty(names: string[]): FileContent | null {
|
|
58
|
+
for (const name of names) {
|
|
59
|
+
try {
|
|
60
|
+
const text = fs.readFileSync(path.join(resultDir, name), 'utf8');
|
|
61
|
+
if (text.trim()) return { name, text };
|
|
62
|
+
} catch {
|
|
63
|
+
// ignore files that cannot be read at decision time
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeExitCode(value: any): number | null {
|
|
70
|
+
return normalizeExitCodeCandidate(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printableExitCode(value: any): string {
|
|
74
|
+
const normalized = normalizeExitCode(value);
|
|
75
|
+
return normalized === null ? 'unknown' : String(normalized);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printList(title: string, values: string[]): void {
|
|
79
|
+
console.log(`${title}:`);
|
|
80
|
+
if (values.length === 0) {
|
|
81
|
+
console.log(' none');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const value of values) console.log(` ${value}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(resultDir)) {
|
|
88
|
+
console.error(`Result directory not found: ${resultDir}`);
|
|
89
|
+
process.exit(2);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const metadata: Metadata = readJson('metadata.json');
|
|
93
|
+
const summary: PiSummary = readJson('pi-summary.json');
|
|
94
|
+
const changedFiles = readText('changed-files.txt')
|
|
95
|
+
.split(/\r?\n/)
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
const timings = readText('validation-timings.tsv')
|
|
98
|
+
.split(/\r?\n/)
|
|
99
|
+
.filter(Boolean);
|
|
100
|
+
const stageTimings = readText('stage-timings.tsv')
|
|
101
|
+
.split(/\r?\n/)
|
|
102
|
+
.filter(Boolean);
|
|
103
|
+
const dependencyCache = readText('dependency-cache.log')
|
|
104
|
+
.split(/\r?\n/)
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
const secretScanBytes = Buffer.byteLength(readText('secret-scan.log'));
|
|
107
|
+
|
|
108
|
+
// Parse restoration.jsonl for metrics
|
|
109
|
+
interface RestorationEvent {
|
|
110
|
+
status: 'restored' | 'kept';
|
|
111
|
+
file: string;
|
|
112
|
+
[key: string]: any;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseRestorationMetrics(): { restored: number; kept: number } {
|
|
116
|
+
const restorationJsonl = readText('restoration.jsonl');
|
|
117
|
+
if (!restorationJsonl.trim()) return { restored: 0, kept: 0 };
|
|
118
|
+
|
|
119
|
+
let restored = 0, kept = 0;
|
|
120
|
+
for (const line of restorationJsonl.split('\n')) {
|
|
121
|
+
if (!line.trim()) continue;
|
|
122
|
+
try {
|
|
123
|
+
const event = JSON.parse(line) as RestorationEvent;
|
|
124
|
+
if (event.status === 'restored') restored++;
|
|
125
|
+
else if (event.status === 'kept') kept++;
|
|
126
|
+
} catch {
|
|
127
|
+
// ignore malformed lines
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { restored, kept };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { restored: restoredCount, kept: keptCount } = parseRestorationMetrics();
|
|
134
|
+
const normalizedExitCode = normalizeExitCode(metadata.exit_code);
|
|
135
|
+
const status = normalizedExitCode === 0 ? 'passed' : 'failed';
|
|
136
|
+
const resultName = metadata.instance || path.basename(resultDir);
|
|
137
|
+
const nextDiagnostic =
|
|
138
|
+
normalizedExitCode === 0
|
|
139
|
+
? 'none'
|
|
140
|
+
: firstReadableNonEmpty([
|
|
141
|
+
'failure.json',
|
|
142
|
+
'quality.log',
|
|
143
|
+
'secret-scan.log',
|
|
144
|
+
'pi-stderr.log',
|
|
145
|
+
'validation.log',
|
|
146
|
+
'preflight-git.log',
|
|
147
|
+
'stderr.log',
|
|
148
|
+
])?.name ?? 'metadata.json';
|
|
149
|
+
|
|
150
|
+
console.log(`Kaseki result: ${resultName}`);
|
|
151
|
+
console.log(`Status: ${status}`);
|
|
152
|
+
console.log(`Failed command: ${metadata.failed_command || 'none'}`);
|
|
153
|
+
console.log(`Exit code: ${printableExitCode(metadata.exit_code)}`);
|
|
154
|
+
console.log(`Pi exit code: ${printableExitCode(metadata.pi_exit_code)}`);
|
|
155
|
+
console.log(
|
|
156
|
+
`Validation exit code: ${printableExitCode(metadata.validation_exit_code)}`
|
|
157
|
+
);
|
|
158
|
+
console.log(
|
|
159
|
+
`Validation failed command: ${metadata.validation_failed_command || 'none'}`
|
|
160
|
+
);
|
|
161
|
+
if (metadata.validation_stopped_early) {
|
|
162
|
+
console.log(
|
|
163
|
+
`⚠️ Validation stopped early (fail-fast mode): ${metadata.validation_commands_attempted ?? 'unknown'} command(s) attempted`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
console.log(`Quality exit code: ${printableExitCode(metadata.quality_exit_code)}`);
|
|
167
|
+
console.log(
|
|
168
|
+
`Secret scan exit code: ${printableExitCode(metadata.secret_scan_exit_code)}`
|
|
169
|
+
);
|
|
170
|
+
console.log(`Requested model: ${metadata.model || 'unknown'}`);
|
|
171
|
+
console.log(
|
|
172
|
+
`Actual model: ${metadata.actual_model || summary.selected_model || 'unknown'}`
|
|
173
|
+
);
|
|
174
|
+
console.log(`Pi version: ${metadata.pi_version || 'unknown'}`);
|
|
175
|
+
console.log(`Duration seconds: ${metadata.duration_seconds ?? 'unknown'}`);
|
|
176
|
+
console.log(
|
|
177
|
+
`Agent duration seconds: ${metadata.pi_duration_seconds ?? 'unknown'}`
|
|
178
|
+
);
|
|
179
|
+
console.log(`Diff non-empty: ${metadata.diff_nonempty ?? 'unknown'}`);
|
|
180
|
+
printList('Changed files', changedFiles);
|
|
181
|
+
if (restoredCount > 0 || keptCount > 0) {
|
|
182
|
+
const totalFiles = restoredCount + keptCount;
|
|
183
|
+
const coverage = totalFiles > 0 ? Math.round((keptCount * 100) / totalFiles) : 0;
|
|
184
|
+
console.log(`Allowlist coverage: ${keptCount}/${totalFiles} files (${coverage}%)`);
|
|
185
|
+
console.log(`Files restored: ${restoredCount}`);
|
|
186
|
+
console.log(`Files kept (allowlist match): ${keptCount}`);
|
|
187
|
+
}
|
|
188
|
+
printList('Stage timings', stageTimings);
|
|
189
|
+
printList('Validation timings', timings);
|
|
190
|
+
printList('Dependency cache', dependencyCache);
|
|
191
|
+
console.log(`Secret scan bytes: ${secretScanBytes}`);
|
|
192
|
+
console.log(`Next diagnostic: ${nextDiagnostic}`);
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared subprocess execution utilities and patterns.
|
|
3
|
+
* Consolidates error handling, timeouts, and Docker-specific logic
|
|
4
|
+
* to reduce duplication across job-scheduler.ts, kaseki-api-routes.ts, and file-helpers.ts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync, SpawnSyncReturns } from 'child_process';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result of a subprocess execution.
|
|
11
|
+
*/
|
|
12
|
+
export interface SubprocessResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status?: number;
|
|
15
|
+
stdout?: string;
|
|
16
|
+
stderr?: string;
|
|
17
|
+
detail?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Classification of a Docker command failure with remediation steps.
|
|
23
|
+
*/
|
|
24
|
+
export interface DockerFailureClassification {
|
|
25
|
+
detail: string;
|
|
26
|
+
remediation: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default timeout for subprocess operations in milliseconds.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Classify a Docker command failure based on error message.
|
|
36
|
+
* Returns actionable detail and remediation information.
|
|
37
|
+
*
|
|
38
|
+
* @param stderr Error output from Docker command
|
|
39
|
+
* @returns Object with detail and remediation strings
|
|
40
|
+
*/
|
|
41
|
+
export function classifyDockerFailure(stderr: string): DockerFailureClassification {
|
|
42
|
+
const normalized = stderr.toLowerCase();
|
|
43
|
+
if (normalized.includes('permission denied') || normalized.includes('connect: permission denied')) {
|
|
44
|
+
return {
|
|
45
|
+
detail: 'Docker daemon socket is not accessible from the API process.',
|
|
46
|
+
remediation:
|
|
47
|
+
'Add the API container user to the host Docker socket group, for example group_add: ["${DOCKER_GID:-985}"].',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (normalized.includes('cannot connect') || normalized.includes('is the docker daemon running')) {
|
|
51
|
+
return {
|
|
52
|
+
detail: 'Docker daemon is unreachable from the API process.',
|
|
53
|
+
remediation: 'Mount /var/run/docker.sock and verify the host Docker daemon is running.',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
detail: stderr.trim() || 'Docker command failed.',
|
|
58
|
+
remediation: 'Verify Docker CLI, daemon access, and the mounted Docker socket.',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract error message from a SpawnSyncReturns result.
|
|
64
|
+
* Prioritizes stderr, then stdout, then error message.
|
|
65
|
+
*
|
|
66
|
+
* @param result SpawnSync result object
|
|
67
|
+
* @returns Combined error message string
|
|
68
|
+
*/
|
|
69
|
+
export function extractErrorMessage(result: SpawnSyncReturns<string | Buffer>): string {
|
|
70
|
+
const parts: string[] = [];
|
|
71
|
+
if (result.stderr) {
|
|
72
|
+
parts.push(typeof result.stderr === 'string' ? result.stderr : result.stderr.toString());
|
|
73
|
+
}
|
|
74
|
+
if (result.stdout && !result.stderr) {
|
|
75
|
+
parts.push(typeof result.stdout === 'string' ? result.stdout : result.stdout.toString());
|
|
76
|
+
}
|
|
77
|
+
if (result.error?.message && !parts.length) {
|
|
78
|
+
parts.push(result.error.message);
|
|
79
|
+
}
|
|
80
|
+
return parts.join(' ').trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Execute a shell command with standard error handling and timeout.
|
|
85
|
+
* Returns success/failure status with combined output.
|
|
86
|
+
*
|
|
87
|
+
* @param command Command to execute
|
|
88
|
+
* @param args Arguments to pass
|
|
89
|
+
* @param options.cwd Working directory (optional)
|
|
90
|
+
* @param options.timeoutMs Timeout in milliseconds (default: 5000)
|
|
91
|
+
* @returns SubprocessResult with status, output, and detail
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const result = execSubprocess('docker', ['version']);
|
|
95
|
+
* if (result.ok) {
|
|
96
|
+
* console.log(result.stdout);
|
|
97
|
+
* } else {
|
|
98
|
+
* console.error(result.detail);
|
|
99
|
+
* }
|
|
100
|
+
*/
|
|
101
|
+
export function execSubprocess(
|
|
102
|
+
command: string,
|
|
103
|
+
args: string[],
|
|
104
|
+
options?: {
|
|
105
|
+
cwd?: string;
|
|
106
|
+
timeoutMs?: number;
|
|
107
|
+
},
|
|
108
|
+
): SubprocessResult {
|
|
109
|
+
const result = spawnSync(command, args, {
|
|
110
|
+
cwd: options?.cwd,
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
timeout: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const detail = extractErrorMessage(result);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
ok: result.status === 0,
|
|
119
|
+
status: result.status ?? undefined,
|
|
120
|
+
stdout: typeof result.stdout === 'string' ? result.stdout.trim() || undefined : undefined,
|
|
121
|
+
stderr: typeof result.stderr === 'string' ? result.stderr.trim() || undefined : undefined,
|
|
122
|
+
detail: detail || undefined,
|
|
123
|
+
error: result.error?.message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Execute a Docker command with error classification and timeout.
|
|
129
|
+
* Automatically classifies Docker-specific error messages.
|
|
130
|
+
*
|
|
131
|
+
* @param args Docker command arguments (e.g., ['version', '--format', '{{.Client.Version}}'])
|
|
132
|
+
* @param timeoutMs Timeout in milliseconds (default: 5000)
|
|
133
|
+
* @returns Object with status, output, and Docker-specific error classification
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* const result = execDockerCommand(['version']);
|
|
137
|
+
* if (result.ok) {
|
|
138
|
+
* console.log(result.stdout);
|
|
139
|
+
* } else {
|
|
140
|
+
* console.error(result.classification.remediation);
|
|
141
|
+
* }
|
|
142
|
+
*/
|
|
143
|
+
export function execDockerCommand(
|
|
144
|
+
args: string[],
|
|
145
|
+
timeoutMs?: number,
|
|
146
|
+
): SubprocessResult & { classification?: DockerFailureClassification } {
|
|
147
|
+
const result = execSubprocess('docker', args, { timeoutMs });
|
|
148
|
+
|
|
149
|
+
if (!result.ok && result.detail) {
|
|
150
|
+
return {
|
|
151
|
+
...result,
|
|
152
|
+
classification: classifyDockerFailure(result.detail),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Execute a command and return only its stdout output on success, undefined on failure.
|
|
161
|
+
* Useful for extracting single-value outputs (version strings, git refs, etc.).
|
|
162
|
+
*
|
|
163
|
+
* @param command Command to execute
|
|
164
|
+
* @param args Arguments to pass
|
|
165
|
+
* @param cwd Working directory (optional)
|
|
166
|
+
* @returns Trimmed stdout on success, undefined on failure
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* const version = commandOutput('git', ['rev-parse', '--short', 'HEAD']);
|
|
170
|
+
* if (version) {
|
|
171
|
+
* console.log('Commit:', version);
|
|
172
|
+
* }
|
|
173
|
+
*/
|
|
174
|
+
export function commandOutput(command: string, args: string[], cwd?: string): string | undefined {
|
|
175
|
+
const result = execSubprocess(command, args, { cwd });
|
|
176
|
+
return result.ok ? result.stdout : undefined;
|
|
177
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging utility with log level filtering.
|
|
3
|
+
*
|
|
4
|
+
* Emits structured JSON events to stdout, respecting LOG_LEVEL env var.
|
|
5
|
+
* All timestamps are ISO 8601 format.
|
|
6
|
+
*
|
|
7
|
+
* Log levels: debug < info < warn < error
|
|
8
|
+
* - DEBUG: All messages
|
|
9
|
+
* - INFO: info, warn, error (default)
|
|
10
|
+
* - WARN: warn, error
|
|
11
|
+
* - ERROR: error only
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
15
|
+
|
|
16
|
+
const LOG_LEVEL_ORDER: Record<LogLevel, number> = {
|
|
17
|
+
debug: 0,
|
|
18
|
+
info: 1,
|
|
19
|
+
warn: 2,
|
|
20
|
+
error: 3,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the log level from environment variable with default.
|
|
25
|
+
*/
|
|
26
|
+
function getLogLevel(): LogLevel {
|
|
27
|
+
const envLevel = process.env.LOG_LEVEL || process.env.KASEKI_API_LOG_LEVEL || 'info';
|
|
28
|
+
const normalized = envLevel.toLowerCase() as LogLevel;
|
|
29
|
+
|
|
30
|
+
if (normalized in LOG_LEVEL_ORDER) {
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.error(`Invalid LOG_LEVEL: ${envLevel}, defaulting to info`);
|
|
35
|
+
return 'info';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Logger instance for a component.
|
|
40
|
+
*/
|
|
41
|
+
export interface Logger {
|
|
42
|
+
debug(message: string, detail?: Record<string, unknown>): void;
|
|
43
|
+
info(message: string, detail?: Record<string, unknown>): void;
|
|
44
|
+
warn(message: string, detail?: Record<string, unknown>): void;
|
|
45
|
+
error(message: string, detail?: Record<string, unknown>): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a logger for a component.
|
|
50
|
+
*/
|
|
51
|
+
export function createLogger(component: string): Logger {
|
|
52
|
+
const currentLogLevel = getLogLevel();
|
|
53
|
+
const currentLevelOrder = LOG_LEVEL_ORDER[currentLogLevel];
|
|
54
|
+
|
|
55
|
+
function shouldLog(messageLevel: LogLevel): boolean {
|
|
56
|
+
return LOG_LEVEL_ORDER[messageLevel] >= currentLevelOrder;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function emit(level: LogLevel, message: string, detail?: Record<string, unknown>): void {
|
|
60
|
+
if (!shouldLog(level)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const event = {
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
level,
|
|
67
|
+
component,
|
|
68
|
+
message,
|
|
69
|
+
...(detail && Object.keys(detail).length > 0 && { detail }),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
console.log(JSON.stringify(event));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
debug: (message: string, detail?: Record<string, unknown>) => emit('debug', message, detail),
|
|
77
|
+
info: (message: string, detail?: Record<string, unknown>) => emit('info', message, detail),
|
|
78
|
+
warn: (message: string, detail?: Record<string, unknown>) => emit('warn', message, detail),
|
|
79
|
+
error: (message: string, detail?: Record<string, unknown>) => emit('error', message, detail),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a logger that can also emit structured events (for progress.jsonl format).
|
|
85
|
+
*
|
|
86
|
+
* Events are JSON objects with timestamp, component, event_type, and optional detail.
|
|
87
|
+
* This is used for structured operational events (job_submitted, stage_started, etc.).
|
|
88
|
+
*/
|
|
89
|
+
export interface EventLogger extends Logger {
|
|
90
|
+
event(eventType: string, detail?: Record<string, unknown>): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create an event-capable logger.
|
|
95
|
+
*/
|
|
96
|
+
export function createEventLogger(component: string): EventLogger {
|
|
97
|
+
const baseLogger = createLogger(component);
|
|
98
|
+
|
|
99
|
+
function emitEvent(eventType: string, detail?: Record<string, unknown>): void {
|
|
100
|
+
const event = {
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
component,
|
|
103
|
+
event_type: eventType,
|
|
104
|
+
...(detail && Object.keys(detail).length > 0 && detail),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
console.log(JSON.stringify(event));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
...baseLogger,
|
|
112
|
+
event: (eventType: string, detail?: Record<string, unknown>) => emitEvent(eventType, detail),
|
|
113
|
+
};
|
|
114
|
+
}
|
package/src/metrics.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export class MetricsRegistry {
|
|
2
|
+
private queuePending = 0;
|
|
3
|
+
private runningJobs = 0;
|
|
4
|
+
private runsTotal = { success: 0, failure: 0 };
|
|
5
|
+
private timeoutsTotal = 0;
|
|
6
|
+
private readonly durationBuckets = [0.5, 1, 2.5, 5, 10, 30, 60, 120, 300, 600, 1800];
|
|
7
|
+
private durationBucketCounts: number[] = this.durationBuckets.map(() => 0);
|
|
8
|
+
private durationSum = 0;
|
|
9
|
+
private durationCount = 0;
|
|
10
|
+
|
|
11
|
+
setQueuePending(count: number): void { this.queuePending = Math.max(0, count); }
|
|
12
|
+
setRunningJobs(count: number): void { this.runningJobs = Math.max(0, count); }
|
|
13
|
+
incRunSuccess(): void { this.runsTotal.success += 1; }
|
|
14
|
+
incRunFailure(): void { this.runsTotal.failure += 1; }
|
|
15
|
+
incTimeout(): void { this.timeoutsTotal += 1; }
|
|
16
|
+
|
|
17
|
+
observeRunDuration(seconds: number): void {
|
|
18
|
+
const value = Number.isFinite(seconds) && seconds >= 0 ? seconds : 0;
|
|
19
|
+
this.durationSum += value;
|
|
20
|
+
this.durationCount += 1;
|
|
21
|
+
for (let i = 0; i < this.durationBuckets.length; i += 1) {
|
|
22
|
+
if (value <= this.durationBuckets[i]) {
|
|
23
|
+
for (let j = i; j < this.durationBuckets.length; j += 1) {
|
|
24
|
+
this.durationBucketCounts[j] += 1;
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
renderPrometheus(): string {
|
|
32
|
+
const lines: string[] = [];
|
|
33
|
+
lines.push('# HELP kaseki_queue_pending Number of queued jobs awaiting execution.');
|
|
34
|
+
lines.push('# TYPE kaseki_queue_pending gauge');
|
|
35
|
+
lines.push(`kaseki_queue_pending ${this.queuePending}`);
|
|
36
|
+
lines.push('# HELP kaseki_running_jobs Number of jobs currently running.');
|
|
37
|
+
lines.push('# TYPE kaseki_running_jobs gauge');
|
|
38
|
+
lines.push(`kaseki_running_jobs ${this.runningJobs}`);
|
|
39
|
+
|
|
40
|
+
lines.push('# HELP kaseki_runs_total Total number of completed runs partitioned by outcome.');
|
|
41
|
+
lines.push('# TYPE kaseki_runs_total counter');
|
|
42
|
+
lines.push(`kaseki_runs_total{result="success"} ${this.runsTotal.success}`);
|
|
43
|
+
lines.push(`kaseki_runs_total{result="failure"} ${this.runsTotal.failure}`);
|
|
44
|
+
|
|
45
|
+
lines.push('# HELP kaseki_run_duration_seconds Runtime duration of completed jobs in seconds.');
|
|
46
|
+
lines.push('# TYPE kaseki_run_duration_seconds histogram');
|
|
47
|
+
this.durationBuckets.forEach((bucket, idx) => {
|
|
48
|
+
lines.push(`kaseki_run_duration_seconds_bucket{le="${bucket}"} ${this.durationBucketCounts[idx]}`);
|
|
49
|
+
});
|
|
50
|
+
lines.push(`kaseki_run_duration_seconds_bucket{le="+Inf"} ${this.durationCount}`);
|
|
51
|
+
lines.push(`kaseki_run_duration_seconds_sum ${this.durationSum}`);
|
|
52
|
+
lines.push(`kaseki_run_duration_seconds_count ${this.durationCount}`);
|
|
53
|
+
|
|
54
|
+
lines.push('# HELP kaseki_timeouts_total Total number of timed out runs.');
|
|
55
|
+
lines.push('# TYPE kaseki_timeouts_total counter');
|
|
56
|
+
lines.push(`kaseki_timeouts_total ${this.timeoutsTotal}`);
|
|
57
|
+
lines.push('# HELP kaseki_timeout_rate Ratio of timed out runs to all completed runs.');
|
|
58
|
+
lines.push('# TYPE kaseki_timeout_rate gauge');
|
|
59
|
+
const totalRuns = this.runsTotal.success + this.runsTotal.failure;
|
|
60
|
+
lines.push(`kaseki_timeout_rate ${totalRuns > 0 ? this.timeoutsTotal / totalRuns : 0}`);
|
|
61
|
+
|
|
62
|
+
return `${lines.join('\n')}\n`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const metricsRegistry = new MetricsRegistry();
|