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