@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
package/kaseki-agent.sh
ADDED
|
@@ -0,0 +1,1961 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# NOTE: This script intentionally avoids global `set -e` so each stage can
|
|
3
|
+
# record status/timing artifacts before deciding whether to stop.
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
INSTANCE_NAME="${KASEKI_INSTANCE:-kaseki-unknown}"
|
|
7
|
+
REPO_URL="${REPO_URL:-https://github.com/CyanAutomation/crudmapper}"
|
|
8
|
+
GIT_REF="${GIT_REF:-main}"
|
|
9
|
+
KASEKI_PROVIDER="${KASEKI_PROVIDER:-openrouter}"
|
|
10
|
+
KASEKI_MODEL="${KASEKI_MODEL:-openrouter/free}"
|
|
11
|
+
KASEKI_DRY_RUN="${KASEKI_DRY_RUN:-0}"
|
|
12
|
+
KASEKI_AGENT_TIMEOUT_SECONDS="${KASEKI_AGENT_TIMEOUT_SECONDS:-1200}"
|
|
13
|
+
KASEKI_VALIDATION_COMMANDS="${KASEKI_VALIDATION_COMMANDS-npm run check;npm run test;npm run build}"
|
|
14
|
+
KASEKI_SKIP_MISSING_NPM_SCRIPTS="${KASEKI_SKIP_MISSING_NPM_SCRIPTS:-1}"
|
|
15
|
+
KASEKI_DEBUG_RAW_EVENTS="${KASEKI_DEBUG_RAW_EVENTS:-0}"
|
|
16
|
+
KASEKI_STREAM_PROGRESS="${KASEKI_STREAM_PROGRESS:-1}"
|
|
17
|
+
KASEKI_VALIDATE_AFTER_AGENT_FAILURE="${KASEKI_VALIDATE_AFTER_AGENT_FAILURE:-0}"
|
|
18
|
+
KASEKI_TASK_MODE="${KASEKI_TASK_MODE:-patch}"
|
|
19
|
+
KASEKI_ALLOW_EMPTY_DIFF="${KASEKI_ALLOW_EMPTY_DIFF:-0}"
|
|
20
|
+
KASEKI_CHANGED_FILES_ALLOWLIST="${KASEKI_CHANGED_FILES_ALLOWLIST:-src/lib/parser.ts tests/parser.validation.ts}"
|
|
21
|
+
KASEKI_VALIDATION_ALLOWLIST="${KASEKI_VALIDATION_ALLOWLIST:-}"
|
|
22
|
+
KASEKI_MAX_DIFF_BYTES="${KASEKI_MAX_DIFF_BYTES:-200000}"
|
|
23
|
+
KASEKI_REPO_MEMORY_MODE="${KASEKI_REPO_MEMORY_MODE:-off}"
|
|
24
|
+
KASEKI_REPO_MEMORY_TTL_DAYS="${KASEKI_REPO_MEMORY_TTL_DAYS:-30}"
|
|
25
|
+
KASEKI_REPO_MEMORY_MAX_BYTES="${KASEKI_REPO_MEMORY_MAX_BYTES:-8000}"
|
|
26
|
+
TASK_PROMPT="${TASK_PROMPT:-Make normalizeRole treat a non-string Name fallback safely when FriendlyName is empty or missing. It should fall back to \"Unnamed Role\" instead of preserving arbitrary truthy non-string values. Add or update exactly one compact table-driven Vitest case in tests/parser.validation.ts, with a neutral static test title and no per-case assertion messages or explanatory comments. Do not add broad repeated test blocks. Do not print, inspect, or expose environment variables, secrets, credentials, or API keys. Keep changes limited to the source and test files needed for this fix.}"
|
|
27
|
+
KASEKI_AGENT_GUARDRAILS="${KASEKI_AGENT_GUARDRAILS:-1}"
|
|
28
|
+
KASEKI_RESTORE_DISALLOWED_CHANGES="${KASEKI_RESTORE_DISALLOWED_CHANGES:-1}"
|
|
29
|
+
KASEKI_VALIDATION_FAIL_FAST="${KASEKI_VALIDATION_FAIL_FAST:-1}"
|
|
30
|
+
KASEKI_STRICT_SCRIPT_CHECK="${KASEKI_STRICT_SCRIPT_CHECK:-0}"
|
|
31
|
+
GITHUB_APP_ENABLED="${GITHUB_APP_ENABLED:-0}"
|
|
32
|
+
KASEKI_PUBLISH_MODE="${KASEKI_PUBLISH_MODE:-auto}"
|
|
33
|
+
START_EPOCH="$(date +%s)"
|
|
34
|
+
START_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
35
|
+
CURRENT_STAGE="initializing"
|
|
36
|
+
PI_START_EPOCH=0
|
|
37
|
+
PI_DURATION_SECONDS=0
|
|
38
|
+
PI_VERSION=""
|
|
39
|
+
STATUS=0
|
|
40
|
+
FAILED_COMMAND=""
|
|
41
|
+
PI_EXIT=0
|
|
42
|
+
VALIDATION_EXIT=0
|
|
43
|
+
VALIDATION_FAILED_COMMAND_DETAIL=""
|
|
44
|
+
VALIDATION_FAILURE_REASON=""
|
|
45
|
+
VALIDATION_STOPPED_EARLY=false
|
|
46
|
+
VALIDATION_COMMANDS_ATTEMPTED=0
|
|
47
|
+
DIFF_NONEMPTY=false
|
|
48
|
+
QUALITY_EXIT=0
|
|
49
|
+
QUALITY_FAILURE_REASON=""
|
|
50
|
+
SECRET_SCAN_EXIT=0
|
|
51
|
+
GITHUB_PUSH_EXIT=0
|
|
52
|
+
GITHUB_PR_EXIT=0
|
|
53
|
+
ACTUAL_MODEL="unknown"
|
|
54
|
+
GITHUB_PR_URL=""
|
|
55
|
+
GITHUB_SKIP_REASONS=()
|
|
56
|
+
VALIDATION_TIMINGS_FILE="/results/validation-timings.tsv"
|
|
57
|
+
STAGE_TIMINGS_FILE="/results/stage-timings.tsv"
|
|
58
|
+
DEPENDENCY_CACHE_LOG="/results/dependency-cache.log"
|
|
59
|
+
RAW_EVENTS="/tmp/pi-events.raw.jsonl"
|
|
60
|
+
KASEKI_DEPENDENCY_CACHE_DIR="${KASEKI_DEPENDENCY_CACHE_DIR:-/workspace/.kaseki-cache}"
|
|
61
|
+
KASEKI_DEPENDENCY_RESTORE_MODE="${KASEKI_DEPENDENCY_RESTORE_MODE:-copy}"
|
|
62
|
+
KASEKI_INSTALL_IGNORE_SCRIPTS="${KASEKI_INSTALL_IGNORE_SCRIPTS:-1}"
|
|
63
|
+
KASEKI_NPM_OMIT_DEV="${KASEKI_NPM_OMIT_DEV:-0}"
|
|
64
|
+
KASEKI_IMAGE_DEPENDENCY_CACHE_DIR="${KASEKI_IMAGE_DEPENDENCY_CACHE_DIR:-/opt/kaseki/workspace-cache}"
|
|
65
|
+
KASEKI_LOG_DIR="${KASEKI_LOG_DIR:-/var/log/kaseki}"
|
|
66
|
+
KASEKI_STRICT_HOST_LOGGING="${KASEKI_STRICT_HOST_LOGGING:-0}"
|
|
67
|
+
KASEKI_GIT_CACHE_MODE="${KASEKI_GIT_CACHE_MODE:-mirror}"
|
|
68
|
+
KASEKI_GIT_CACHE_ROOT="${KASEKI_GIT_CACHE_ROOT:-/cache/git}"
|
|
69
|
+
KASEKI_GIT_CACHE_FETCH_TIMEOUT_SECONDS="${KASEKI_GIT_CACHE_FETCH_TIMEOUT_SECONDS:-120}"
|
|
70
|
+
GIT_CACHE_KEY=""
|
|
71
|
+
GIT_CACHE_MIRROR=""
|
|
72
|
+
GIT_CACHE_HIT="false"
|
|
73
|
+
GIT_CACHE_STATUS="not_started"
|
|
74
|
+
GIT_CACHE_MODE_USED="$KASEKI_GIT_CACHE_MODE"
|
|
75
|
+
GIT_CLONE_STRATEGY="not_started"
|
|
76
|
+
GIT_CLONE_DURATION_SECONDS=0
|
|
77
|
+
REPO_MEMORY_KEY=""
|
|
78
|
+
REPO_MEMORY_DIR=""
|
|
79
|
+
REPO_MEMORY_FILE=""
|
|
80
|
+
REPO_MEMORY_STATUS="disabled"
|
|
81
|
+
REPO_MEMORY_COMMIT_SHA="unknown"
|
|
82
|
+
|
|
83
|
+
setup_host_logging_mirror() {
|
|
84
|
+
local base_name="$1"
|
|
85
|
+
local stamp host_log_file
|
|
86
|
+
if mkdir -p "$KASEKI_LOG_DIR" 2>/dev/null && [ -w "$KASEKI_LOG_DIR" ]; then
|
|
87
|
+
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
88
|
+
host_log_file="$KASEKI_LOG_DIR/${base_name}-${stamp}.log"
|
|
89
|
+
exec > >(tee -a /results/stdout.log | tee -a "$host_log_file") \
|
|
90
|
+
2> >(tee -a /results/stderr.log | tee -a "$host_log_file" >&2)
|
|
91
|
+
printf 'Host log mirror: %s\n' "$host_log_file"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
if [ "$KASEKI_STRICT_HOST_LOGGING" = "1" ]; then
|
|
95
|
+
printf 'Error: strict host logging enabled, but KASEKI_LOG_DIR is not writable: %s\n' "$KASEKI_LOG_DIR" >&2
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
exec > >(tee -a /results/stdout.log) 2> >(tee -a /results/stderr.log >&2)
|
|
99
|
+
printf 'Warning: host log mirror disabled; KASEKI_LOG_DIR is unavailable: %s\n' "$KASEKI_LOG_DIR" >&2
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
mkdir_paths=(/results)
|
|
103
|
+
if [ -n "${HOME:-}" ]; then
|
|
104
|
+
mkdir_paths+=("${HOME}")
|
|
105
|
+
fi
|
|
106
|
+
if [ -n "${NPM_CONFIG_CACHE:-}" ]; then
|
|
107
|
+
mkdir_paths+=("${NPM_CONFIG_CACHE}")
|
|
108
|
+
fi
|
|
109
|
+
if [ -n "${TMPDIR:-}" ]; then
|
|
110
|
+
mkdir_paths+=("${TMPDIR}")
|
|
111
|
+
fi
|
|
112
|
+
if [ -n "${PI_CODING_AGENT_DIR:-}" ]; then
|
|
113
|
+
mkdir_paths+=("${PI_CODING_AGENT_DIR}")
|
|
114
|
+
fi
|
|
115
|
+
mkdir -p "${mkdir_paths[@]}"
|
|
116
|
+
PI_VERSION="$(pi --version 2>&1 | head -n 1 || true)"
|
|
117
|
+
: > /results/stdout.log
|
|
118
|
+
: > /results/stderr.log
|
|
119
|
+
: > /results/pi-events.jsonl
|
|
120
|
+
: > /results/pi-summary.json
|
|
121
|
+
: > /results/validation.log
|
|
122
|
+
: > /results/quality.log
|
|
123
|
+
: > /results/secret-scan.log
|
|
124
|
+
: > /results/git-push.log
|
|
125
|
+
: > /results/progress.log
|
|
126
|
+
: > /results/progress.jsonl
|
|
127
|
+
: > /results/format-check-command.txt
|
|
128
|
+
: > /results/failure.json
|
|
129
|
+
: > /results/result-summary.md
|
|
130
|
+
: > "$VALIDATION_TIMINGS_FILE"
|
|
131
|
+
: >> "$STAGE_TIMINGS_FILE"
|
|
132
|
+
: > "$DEPENDENCY_CACHE_LOG"
|
|
133
|
+
setup_host_logging_mirror "$INSTANCE_NAME"
|
|
134
|
+
case "$KASEKI_GIT_CACHE_MODE" in
|
|
135
|
+
off|mirror)
|
|
136
|
+
;;
|
|
137
|
+
*)
|
|
138
|
+
printf 'Warning: unsupported KASEKI_GIT_CACHE_MODE=%s; falling back to off. Expected off or mirror.\n' "$KASEKI_GIT_CACHE_MODE" >&2
|
|
139
|
+
KASEKI_GIT_CACHE_MODE="off"
|
|
140
|
+
GIT_CACHE_MODE_USED="off"
|
|
141
|
+
;;
|
|
142
|
+
esac
|
|
143
|
+
|
|
144
|
+
# Safely encode value as JSON string; fallback to empty string if node unavailable
|
|
145
|
+
json_encode() {
|
|
146
|
+
if ! command -v node &>/dev/null; then
|
|
147
|
+
printf '""' # Return empty JSON string if node is unavailable
|
|
148
|
+
return 1
|
|
149
|
+
fi
|
|
150
|
+
local output
|
|
151
|
+
output=$(node -e 'const chunks=[]; process.stdin.on("data", c => chunks.push(c)); process.stdin.on("end", () => process.stdout.write(JSON.stringify(Buffer.concat(chunks).toString().replace(/\n$/, ""))));' 2>&1)
|
|
152
|
+
local exit_code=$?
|
|
153
|
+
if [ $exit_code -eq 0 ] && [ -n "$output" ]; then
|
|
154
|
+
printf '%s' "$output"
|
|
155
|
+
else
|
|
156
|
+
# Log error and return empty JSON string as fallback
|
|
157
|
+
printf 'warning: json_encode failed (exit %d): %s\n' "$exit_code" "$output" >&2
|
|
158
|
+
printf '""'
|
|
159
|
+
return 1
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
json_array() {
|
|
164
|
+
if ! command -v node &>/dev/null; then
|
|
165
|
+
printf '[]' # Return empty JSON array if node is unavailable
|
|
166
|
+
return 1
|
|
167
|
+
fi
|
|
168
|
+
node -e 'process.stdout.write(JSON.stringify(process.argv.slice(1)));' -- "$@" 2>&1 || printf '[]'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Validate that a variable contains only numeric digits (for use before arithmetic)
|
|
172
|
+
validate_numeric() {
|
|
173
|
+
local var_name="$1"
|
|
174
|
+
local var_value="$2"
|
|
175
|
+
# Empty or missing value is treated as invalid
|
|
176
|
+
if [ -z "$var_value" ]; then
|
|
177
|
+
printf 'error: %s is not numeric (value="%s")\n' "$var_name" "$var_value" >&2
|
|
178
|
+
return 1
|
|
179
|
+
fi
|
|
180
|
+
# Reject any non-digit character, including embedded newlines.
|
|
181
|
+
case "$var_value" in
|
|
182
|
+
*[!0-9]*)
|
|
183
|
+
printf 'error: %s is not a valid integer (value="%s")\n' "$var_name" "$var_value" >&2
|
|
184
|
+
return 1
|
|
185
|
+
;;
|
|
186
|
+
esac
|
|
187
|
+
return 0
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
emit_progress() {
|
|
191
|
+
local stage="$1"
|
|
192
|
+
local detail="$2"
|
|
193
|
+
local status="${3:-info}"
|
|
194
|
+
local now
|
|
195
|
+
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
196
|
+
printf '{"timestamp":%s,"component":%s,"stage":%s,"status":%s,"instance":%s,"detail":%s}\n' \
|
|
197
|
+
"$(printf '%s' "$now" | json_encode)" \
|
|
198
|
+
"$(printf '%s' "kaseki-agent" | json_encode)" \
|
|
199
|
+
"$(printf '%s' "$stage" | json_encode)" \
|
|
200
|
+
"$(printf '%s' "$status" | json_encode)" \
|
|
201
|
+
"$(printf '%s' "$INSTANCE_NAME" | json_encode)" \
|
|
202
|
+
"$(printf '%s' "$detail" | json_encode)" >> /results/progress.jsonl
|
|
203
|
+
printf '[progress] %s %s: %s\n' "$stage" "$status" "$detail" | tee -a /results/progress.log
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
emit_event() {
|
|
207
|
+
local event_type="$1"
|
|
208
|
+
shift
|
|
209
|
+
local detail_json="{}"
|
|
210
|
+
if [ $# -gt 0 ]; then
|
|
211
|
+
# Build detail object from key=value pairs
|
|
212
|
+
local -a pairs=("$@")
|
|
213
|
+
detail_json="{"
|
|
214
|
+
for i in "${!pairs[@]}"; do
|
|
215
|
+
local pair="${pairs[$i]}"
|
|
216
|
+
local key="${pair%%=*}"
|
|
217
|
+
local value="${pair#*=}"
|
|
218
|
+
if [ "$i" -gt 0 ]; then
|
|
219
|
+
detail_json="${detail_json},"
|
|
220
|
+
fi
|
|
221
|
+
detail_json="${detail_json}$(printf '%s' "$key" | json_encode):$(printf '%s' "$value" | json_encode)"
|
|
222
|
+
done
|
|
223
|
+
detail_json="${detail_json}}"
|
|
224
|
+
fi
|
|
225
|
+
local now
|
|
226
|
+
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
227
|
+
printf '{"timestamp":%s,"component":%s,"event_type":%s,"instance":%s,%s}\n' \
|
|
228
|
+
"$(printf '%s' "$now" | json_encode)" \
|
|
229
|
+
"$(printf '%s' "kaseki-agent" | json_encode)" \
|
|
230
|
+
"$(printf '%s' "$event_type" | json_encode)" \
|
|
231
|
+
"$(printf '%s' "$INSTANCE_NAME" | json_encode)" \
|
|
232
|
+
"$(printf '%s' "$detail_json" | sed 's/^{\(.*\)}$/\1/')" >> /results/progress.jsonl
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
emit_error_event() {
|
|
236
|
+
local error_type="$1"
|
|
237
|
+
local detail="$2"
|
|
238
|
+
local recovery="${3:-continue}"
|
|
239
|
+
emit_event "error" "error_type=$error_type" "detail=$detail" "recovery_action=$recovery"
|
|
240
|
+
printf '[error] %s: %s (recovery: %s)\n' "$error_type" "$detail" "$recovery" | tee -a /results/progress.log
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
write_metadata() {
|
|
244
|
+
local end_epoch end_iso duration exit_code
|
|
245
|
+
end_epoch="$(date +%s)"
|
|
246
|
+
end_iso="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
247
|
+
duration=$((end_epoch - START_EPOCH))
|
|
248
|
+
exit_code="${1:-$STATUS}"
|
|
249
|
+
cat > /results/metadata.json <<META
|
|
250
|
+
{
|
|
251
|
+
"instance": $(printf '%s' "$INSTANCE_NAME" | json_encode),
|
|
252
|
+
"repo_url": $(printf '%s' "$REPO_URL" | json_encode),
|
|
253
|
+
"git_ref": $(printf '%s' "$GIT_REF" | json_encode),
|
|
254
|
+
"provider": $(printf '%s' "$KASEKI_PROVIDER" | json_encode),
|
|
255
|
+
"model": $(printf '%s' "$KASEKI_MODEL" | json_encode),
|
|
256
|
+
"task_mode": $(printf '%s' "$KASEKI_TASK_MODE" | json_encode),
|
|
257
|
+
"allow_empty_diff": $(printf '%s' "$KASEKI_ALLOW_EMPTY_DIFF" | json_encode),
|
|
258
|
+
"started_at": $(printf '%s' "$START_ISO" | json_encode),
|
|
259
|
+
"current_stage": $(printf '%s' "$CURRENT_STAGE" | json_encode),
|
|
260
|
+
"ended_at": $(printf '%s' "$end_iso" | json_encode),
|
|
261
|
+
"duration_seconds": $duration,
|
|
262
|
+
"total_duration_seconds": $duration,
|
|
263
|
+
"pi_duration_seconds": $PI_DURATION_SECONDS,
|
|
264
|
+
"exit_code": $exit_code,
|
|
265
|
+
"failed_command": $(printf '%s' "$FAILED_COMMAND" | json_encode),
|
|
266
|
+
"validation_failed_command": $(printf '%s' "$VALIDATION_FAILED_COMMAND_DETAIL" | json_encode),
|
|
267
|
+
"validation_failure_reason": $(printf '%s' "$VALIDATION_FAILURE_REASON" | json_encode),
|
|
268
|
+
"quality_failure_reason": $(printf '%s' "$QUALITY_FAILURE_REASON" | json_encode),
|
|
269
|
+
"pi_exit_code": $PI_EXIT,
|
|
270
|
+
"validation_exit_code": $VALIDATION_EXIT,
|
|
271
|
+
"validation_fail_fast_mode": $([[ "$KASEKI_VALIDATION_FAIL_FAST" == "1" ]] && printf 'true' || printf 'false'),
|
|
272
|
+
"validation_stopped_early": $([[ "$VALIDATION_STOPPED_EARLY" == "true" ]] && printf 'true' || printf 'false'),
|
|
273
|
+
"validation_commands_attempted": $VALIDATION_COMMANDS_ATTEMPTED,
|
|
274
|
+
"quality_exit_code": $QUALITY_EXIT,
|
|
275
|
+
"secret_scan_exit_code": $SECRET_SCAN_EXIT,
|
|
276
|
+
"github_push_exit_code": $GITHUB_PUSH_EXIT,
|
|
277
|
+
"github_pr_exit_code": $GITHUB_PR_EXIT,
|
|
278
|
+
"diff_nonempty": $DIFF_NONEMPTY,
|
|
279
|
+
"actual_model": $(printf '%s' "$ACTUAL_MODEL" | json_encode),
|
|
280
|
+
"github_pr_url": $(printf '%s' "$GITHUB_PR_URL" | json_encode),
|
|
281
|
+
"publish_mode": $(printf '%s' "$KASEKI_PUBLISH_MODE" | json_encode),
|
|
282
|
+
"github_skip_reasons": $(json_array "${GITHUB_SKIP_REASONS[@]}"),
|
|
283
|
+
"git_cache_mode": $(printf '%s' "$GIT_CACHE_MODE_USED" | json_encode),
|
|
284
|
+
"git_cache_status": $(printf '%s' "$GIT_CACHE_STATUS" | json_encode),
|
|
285
|
+
"git_cache_hit": $GIT_CACHE_HIT,
|
|
286
|
+
"git_cache_key": $(printf '%s' "$GIT_CACHE_KEY" | json_encode),
|
|
287
|
+
"git_cache_mirror": $(printf '%s' "$GIT_CACHE_MIRROR" | json_encode),
|
|
288
|
+
"git_clone_strategy": $(printf '%s' "$GIT_CLONE_STRATEGY" | json_encode),
|
|
289
|
+
"git_clone_duration_seconds": $GIT_CLONE_DURATION_SECONDS,
|
|
290
|
+
"repo_memory_mode": $(printf '%s' "$KASEKI_REPO_MEMORY_MODE" | json_encode),
|
|
291
|
+
"repo_memory_status": $(printf '%s' "$REPO_MEMORY_STATUS" | json_encode),
|
|
292
|
+
"repo_memory_key": $(printf '%s' "$REPO_MEMORY_KEY" | json_encode),
|
|
293
|
+
"repo_memory_file": $(printf '%s' "$REPO_MEMORY_FILE" | json_encode),
|
|
294
|
+
"repo_memory_ttl_days": $KASEKI_REPO_MEMORY_TTL_DAYS,
|
|
295
|
+
"repo_memory_max_bytes": $KASEKI_REPO_MEMORY_MAX_BYTES,
|
|
296
|
+
"node_version": $(node --version 2>/dev/null | json_encode || printf 'null'),
|
|
297
|
+
"npm_version": $(npm --version 2>/dev/null | json_encode || printf 'null'),
|
|
298
|
+
"pi_version": $(printf '%s' "$PI_VERSION" | json_encode)
|
|
299
|
+
}
|
|
300
|
+
META
|
|
301
|
+
printf '%s\n' "$exit_code" > /results/exit_code
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
set_current_stage() {
|
|
305
|
+
CURRENT_STAGE="$1"
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
write_result_summary() {
|
|
309
|
+
local changed_files changed_files_markdown validation_status pr_status github_skip_reasons_summary
|
|
310
|
+
changed_files="$(cat /results/changed-files.txt 2>/dev/null || true)"
|
|
311
|
+
if [ -n "$changed_files" ]; then
|
|
312
|
+
changed_files_markdown="$(printf '%s\n' "$changed_files" | sed 's/^/ - /')"
|
|
313
|
+
else
|
|
314
|
+
changed_files_markdown=" - none"
|
|
315
|
+
fi
|
|
316
|
+
validation_status="passed"
|
|
317
|
+
[ "$VALIDATION_EXIT" -ne 0 ] && validation_status="failed"
|
|
318
|
+
if grep -q 'skipped_after_agent_failure' "$STAGE_TIMINGS_FILE" 2>/dev/null; then
|
|
319
|
+
validation_status="skipped"
|
|
320
|
+
fi
|
|
321
|
+
github_skip_reasons_summary="none"
|
|
322
|
+
if [ "${#GITHUB_SKIP_REASONS[@]}" -gt 0 ]; then
|
|
323
|
+
github_skip_reasons_summary="$(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")"
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
pr_status="not attempted"
|
|
327
|
+
if [ "${#GITHUB_SKIP_REASONS[@]}" -gt 0 ]; then
|
|
328
|
+
pr_status="not attempted (reasons: $github_skip_reasons_summary)"
|
|
329
|
+
fi
|
|
330
|
+
if [ "$GITHUB_APP_ENABLED" = "1" ] && [ "${#GITHUB_SKIP_REASONS[@]}" -eq 0 ]; then
|
|
331
|
+
if [ "$GITHUB_PUSH_EXIT" -ne 0 ]; then
|
|
332
|
+
pr_status="push failed"
|
|
333
|
+
elif [ "$GITHUB_PR_EXIT" -eq 0 ] && [ -n "$GITHUB_PR_URL" ]; then
|
|
334
|
+
pr_status="created ($GITHUB_PR_URL)"
|
|
335
|
+
elif [ "$GITHUB_PR_EXIT" -ne 0 ]; then
|
|
336
|
+
pr_status="pr creation failed"
|
|
337
|
+
else
|
|
338
|
+
pr_status="push succeeded, pr not created"
|
|
339
|
+
fi
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
cat > /results/result-summary.md <<SUMMARY
|
|
343
|
+
# Kaseki Result: $INSTANCE_NAME
|
|
344
|
+
|
|
345
|
+
- Status: $(if [ "$STATUS" -eq 0 ]; then printf 'passed'; else printf 'failed'; fi)
|
|
346
|
+
- Failed command: ${FAILED_COMMAND:-none}
|
|
347
|
+
- Requested model: $KASEKI_MODEL
|
|
348
|
+
- Actual model: ${ACTUAL_MODEL:-unknown}
|
|
349
|
+
- Pi exit code: $PI_EXIT
|
|
350
|
+
- Validation: $validation_status ($VALIDATION_EXIT)
|
|
351
|
+
$(if [ -n "$VALIDATION_FAILURE_REASON" ]; then printf ' - Reason: %s\n' "$VALIDATION_FAILURE_REASON"; fi)
|
|
352
|
+
- Validation failure detail: ${VALIDATION_FAILED_COMMAND_DETAIL:-none}
|
|
353
|
+
$(if [ "$VALIDATION_STOPPED_EARLY" = "true" ]; then printf '- **⚠️ Validation stopped early** (fail-fast mode): %s of %s commands ran\n' "$(printf '%s' "${VALIDATION_COMMANDS[@]}" | wc -w)" "$(echo "$KASEKI_VALIDATION_COMMANDS" | tr ';' '\n' | grep -c .)"; fi)
|
|
354
|
+
- Quality checks: $QUALITY_EXIT
|
|
355
|
+
- Secret scan: $SECRET_SCAN_EXIT
|
|
356
|
+
- GitHub PR: $pr_status
|
|
357
|
+
- GitHub skip reasons: $github_skip_reasons_summary
|
|
358
|
+
- Diff non-empty: $DIFF_NONEMPTY
|
|
359
|
+
- Changed files:
|
|
360
|
+
$changed_files_markdown
|
|
361
|
+
|
|
362
|
+
Artifacts:
|
|
363
|
+
- metadata.json
|
|
364
|
+
- pi-summary.json
|
|
365
|
+
- pi-events.jsonl
|
|
366
|
+
- validation.log
|
|
367
|
+
- validation-timings.tsv
|
|
368
|
+
- stage-timings.tsv
|
|
369
|
+
- dependency-cache.log
|
|
370
|
+
- git.diff
|
|
371
|
+
- git.status
|
|
372
|
+
- git-push.log (if GitHub App enabled)
|
|
373
|
+
- progress.log
|
|
374
|
+
- progress.jsonl
|
|
375
|
+
- cleanup.log (host artifact)
|
|
376
|
+
SUMMARY
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
write_failure_json() {
|
|
380
|
+
local exit_code="$1"
|
|
381
|
+
local stderr_tail
|
|
382
|
+
stderr_tail="$(tail -20 /results/stderr.log 2>/dev/null || true)"
|
|
383
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
384
|
+
: > /results/failure.json
|
|
385
|
+
return 0
|
|
386
|
+
fi
|
|
387
|
+
cat > /results/failure.json <<FAILURE
|
|
388
|
+
{
|
|
389
|
+
"instance": $(printf '%s' "$INSTANCE_NAME" | json_encode),
|
|
390
|
+
"exit_code": $exit_code,
|
|
391
|
+
"failed_command": $(printf '%s' "$FAILED_COMMAND" | json_encode),
|
|
392
|
+
"validation_failed_command": $(printf '%s' "$VALIDATION_FAILED_COMMAND_DETAIL" | json_encode),
|
|
393
|
+
"validation_failure_reason": $(printf '%s' "$VALIDATION_FAILURE_REASON" | json_encode),
|
|
394
|
+
"quality_failure_reason": $(printf '%s' "$QUALITY_FAILURE_REASON" | json_encode),
|
|
395
|
+
"stage": $(printf '%s' "$CURRENT_STAGE" | json_encode),
|
|
396
|
+
"stderr_tail": $(printf '%s' "$stderr_tail" | json_encode),
|
|
397
|
+
"artifacts_dir": "/results",
|
|
398
|
+
"metadata": "metadata.json",
|
|
399
|
+
"stderr": "stderr.log",
|
|
400
|
+
"stdout": "stdout.log",
|
|
401
|
+
"progress": "progress.jsonl",
|
|
402
|
+
"summary": "result-summary.md"
|
|
403
|
+
}
|
|
404
|
+
FAILURE
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
collect_git_artifacts() {
|
|
408
|
+
DIFF_NONEMPTY=false
|
|
409
|
+
if [ -d /workspace/repo/.git ]; then
|
|
410
|
+
while IFS= read -r untracked_file || [ -n "$untracked_file" ]; do
|
|
411
|
+
[ -z "$untracked_file" ] && continue
|
|
412
|
+
git -C /workspace/repo add -N -- "$untracked_file" 2>/dev/null || true
|
|
413
|
+
done < <(git -C /workspace/repo ls-files --others --exclude-standard 2>/dev/null || true)
|
|
414
|
+
git -C /workspace/repo status --short > /results/git.status 2>/dev/null || true
|
|
415
|
+
git -C /workspace/repo diff -- . > /results/git.diff 2>/dev/null || true
|
|
416
|
+
git -C /workspace/repo diff --name-only -- . > /results/changed-files.txt 2>/dev/null || true
|
|
417
|
+
if [ -s /results/git.diff ]; then
|
|
418
|
+
DIFF_NONEMPTY=true
|
|
419
|
+
fi
|
|
420
|
+
else
|
|
421
|
+
: > /results/git.status
|
|
422
|
+
: > /results/git.diff
|
|
423
|
+
: > /results/changed-files.txt
|
|
424
|
+
fi
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
428
|
+
ALLOWLIST_HELPER="$SCRIPT_DIR/scripts/allowlist-helper.sh"
|
|
429
|
+
if [ ! -r "$ALLOWLIST_HELPER" ] && [ -r /app/scripts/allowlist-helper.sh ]; then
|
|
430
|
+
ALLOWLIST_HELPER="/app/scripts/allowlist-helper.sh"
|
|
431
|
+
fi
|
|
432
|
+
# shellcheck source=scripts/allowlist-helper.sh
|
|
433
|
+
. "$ALLOWLIST_HELPER"
|
|
434
|
+
|
|
435
|
+
restore_disallowed_changes() {
|
|
436
|
+
if [ "$KASEKI_RESTORE_DISALLOWED_CHANGES" != "1" ] || [ ! -d /workspace/repo/.git ]; then
|
|
437
|
+
return 0
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
local allowlist_regex restored_any restored_count kept_count coverage
|
|
441
|
+
allowlist_regex="$(build_allowlist_regex)"
|
|
442
|
+
[ -z "$allowlist_regex" ] && return 0
|
|
443
|
+
restored_any=0
|
|
444
|
+
restored_count=0
|
|
445
|
+
kept_count=0
|
|
446
|
+
coverage=0
|
|
447
|
+
|
|
448
|
+
# Initialize restoration tracking file
|
|
449
|
+
: > /results/restoration.jsonl
|
|
450
|
+
|
|
451
|
+
while IFS= read -r changed_file || [ -n "$changed_file" ]; do
|
|
452
|
+
[ -z "$changed_file" ] && continue
|
|
453
|
+
if printf '%s\n' "$changed_file" | grep -Eq "^(${allowlist_regex})$"; then
|
|
454
|
+
# File matched allowlist - keep it
|
|
455
|
+
kept_count=$((kept_count + 1))
|
|
456
|
+
{
|
|
457
|
+
printf '{"timestamp":"%s","event":"file_evaluated","file":"%s","status":"kept","reason":"matched_allowlist"}\n' \
|
|
458
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(printf '%s' "$changed_file" | sed 's/"/\\"/g')"
|
|
459
|
+
} >> /results/restoration.jsonl
|
|
460
|
+
continue
|
|
461
|
+
fi
|
|
462
|
+
# File did not match allowlist - restore it
|
|
463
|
+
restored_count=$((restored_count + 1))
|
|
464
|
+
printf -- 'Restoring changed file outside allowlist before validation: %s\n' "$changed_file" | tee -a /results/quality.log
|
|
465
|
+
emit_event "quality_gate_rule_evaluated" "rule=allowlist_restore" "passed=true" "file=$changed_file"
|
|
466
|
+
{
|
|
467
|
+
printf '{"timestamp":"%s","event":"file_restored","file":"%s","status":"restored","reason":"not_in_allowlist"}\n' \
|
|
468
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(printf '%s' "$changed_file" | sed 's/"/\\"/g')"
|
|
469
|
+
} >> /results/restoration.jsonl
|
|
470
|
+
git -C /workspace/repo restore --staged --worktree -- "$changed_file" 2>/dev/null || true
|
|
471
|
+
git -C /workspace/repo clean -f -- "$changed_file" 2>/dev/null || true
|
|
472
|
+
restored_any=1
|
|
473
|
+
done < /results/changed-files.txt
|
|
474
|
+
|
|
475
|
+
# Emit restoration summary to quality.log with actionable guidance
|
|
476
|
+
if [ $((restored_count + kept_count)) -gt 0 ]; then
|
|
477
|
+
coverage=$((kept_count * 100 / (restored_count + kept_count)))
|
|
478
|
+
fi
|
|
479
|
+
if [ "$restored_count" -gt 0 ] || [ "$kept_count" -gt 0 ]; then
|
|
480
|
+
{
|
|
481
|
+
printf '\n[allowlist summary] Restored: %d files; Kept: %d files (coverage: %d%%)\n' "$restored_count" "$kept_count" "$coverage"
|
|
482
|
+
if [ "$restored_count" -gt 0 ] && [ "$coverage" -lt 50 ]; then
|
|
483
|
+
printf '[allowlist note] Low coverage detected. To improve:\n'
|
|
484
|
+
printf ' 1. Run: ./scripts/suggest-allowlist.sh /results (or /agents/kaseki-results/<instance>)\n'
|
|
485
|
+
printf ' 2. Review suggested patterns in allowlist-suggestions.md\n'
|
|
486
|
+
printf ' 3. Update KASEKI_CHANGED_FILES_ALLOWLIST and re-run\n'
|
|
487
|
+
printf 'See docs/QUALITY_GATES.md for more guidance.\n'
|
|
488
|
+
fi
|
|
489
|
+
} | tee -a /results/quality.log
|
|
490
|
+
emit_event "allowlist_restoration_complete" "restored=$restored_count" "kept=$kept_count" "coverage=$coverage"
|
|
491
|
+
fi
|
|
492
|
+
|
|
493
|
+
if [ "$restored_any" -eq 1 ]; then
|
|
494
|
+
collect_git_artifacts
|
|
495
|
+
fi
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
generate_restoration_report() {
|
|
499
|
+
if [ ! -f /results/restoration.jsonl ]; then
|
|
500
|
+
printf '[debug] restoration report: skipping - restoration.jsonl not found\n' >&2
|
|
501
|
+
return 0
|
|
502
|
+
fi
|
|
503
|
+
|
|
504
|
+
local restored_count kept_count total_count coverage_pct
|
|
505
|
+
|
|
506
|
+
# Safely extract counts from restoration.jsonl with validation
|
|
507
|
+
printf '[debug] restoration report: extracting counts from restoration.jsonl\n' >&2
|
|
508
|
+
restored_count=$(grep -c '"status":"restored"' /results/restoration.jsonl 2>/dev/null || true)
|
|
509
|
+
restored_count=${restored_count:-0}
|
|
510
|
+
printf '[debug] restoration report: restored_count="%s"\n' "$restored_count" >&2
|
|
511
|
+
if ! validate_numeric "restored_count" "$restored_count"; then
|
|
512
|
+
printf 'warning: restoration report generation failed - restored_count validation failed\n' >&2
|
|
513
|
+
return 1
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
kept_count=$(grep -c '"status":"kept"' /results/restoration.jsonl 2>/dev/null || true)
|
|
517
|
+
kept_count=${kept_count:-0}
|
|
518
|
+
printf '[debug] restoration report: kept_count="%s"\n' "$kept_count" >&2
|
|
519
|
+
if ! validate_numeric "kept_count" "$kept_count"; then
|
|
520
|
+
printf 'warning: restoration report generation failed - kept_count validation failed\n' >&2
|
|
521
|
+
return 1
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
# Arithmetic operation - now guaranteed to have valid numeric values
|
|
525
|
+
printf '[debug] restoration report: computing total_count from restored=%s and kept=%s\n' "$restored_count" "$kept_count" >&2
|
|
526
|
+
total_count=$((restored_count + kept_count))
|
|
527
|
+
printf '[debug] restoration report: total_count="%s"\n' "$total_count" >&2
|
|
528
|
+
|
|
529
|
+
if [ "$total_count" -eq 0 ]; then
|
|
530
|
+
printf '[debug] restoration report: no changes recorded, skipping report\n' >&2
|
|
531
|
+
return 0
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
printf '[debug] restoration report: generating report with %d total changes\n' "$total_count" >&2
|
|
535
|
+
|
|
536
|
+
{
|
|
537
|
+
printf '# Allowlist Restoration Report\n\n'
|
|
538
|
+
printf 'Generated: %s\n\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
539
|
+
printf '## Summary\n\n'
|
|
540
|
+
# All variables are now validated as numeric by validate_numeric() above
|
|
541
|
+
printf -- '- **Total Files Changed:** %d\n' "$total_count" || { printf 'error: failed to write total count\n' >&2; return 1; }
|
|
542
|
+
printf -- '- **Files Kept (in allowlist):** %d\n' "$kept_count" || { printf 'error: failed to write kept count\n' >&2; return 1; }
|
|
543
|
+
printf -- '- **Files Restored (outside allowlist):** %d\n' "$restored_count" || { printf 'error: failed to write restored count\n' >&2; return 1; }
|
|
544
|
+
if [ "$total_count" -gt 0 ]; then
|
|
545
|
+
# Calculate coverage percentage - safe because total_count is validated as > 0
|
|
546
|
+
coverage_pct=$((kept_count * 100 / total_count))
|
|
547
|
+
printf '[debug] restoration report: coverage_pct=%d (kept=%s / total=%s)\n' "$coverage_pct" "$kept_count" "$total_count" >&2
|
|
548
|
+
printf -- '- **Allowlist Coverage:** %d%%\n\n' "$coverage_pct" || { printf 'error: failed to write coverage pct\n' >&2; return 1; }
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
if [ "$restored_count" -gt 0 ]; then
|
|
552
|
+
printf '## Restored Files\n\n'
|
|
553
|
+
printf 'These files were modified by the agent but restored because they fall outside the allowlist:\n\n'
|
|
554
|
+
grep '"status":"restored"' /results/restoration.jsonl | \
|
|
555
|
+
sed "s/.*\"file\":\"\([^\"]*\)\".*/- \`\1\`/" | \
|
|
556
|
+
sort | uniq >> /results/restoration-report.md.tmp 2>/dev/null || true
|
|
557
|
+
if [ -f /results/restoration-report.md.tmp ]; then
|
|
558
|
+
cat /results/restoration-report.md.tmp
|
|
559
|
+
rm -f /results/restoration-report.md.tmp
|
|
560
|
+
fi
|
|
561
|
+
printf '\n'
|
|
562
|
+
fi
|
|
563
|
+
|
|
564
|
+
if [ "$kept_count" -gt 0 ]; then
|
|
565
|
+
printf '## Kept Files (Allowlist Matches)\n\n'
|
|
566
|
+
printf 'These files were in the allowlist and were kept:\n\n'
|
|
567
|
+
grep '"status":"kept"' /results/restoration.jsonl | \
|
|
568
|
+
sed "s/.*\"file\":\"\([^\"]*\)\".*/- \`\1\`/" | \
|
|
569
|
+
sort | uniq >> /results/restoration-report.md.tmp 2>/dev/null || true
|
|
570
|
+
if [ -f /results/restoration-report.md.tmp ]; then
|
|
571
|
+
cat /results/restoration-report.md.tmp
|
|
572
|
+
rm -f /results/restoration-report.md.tmp
|
|
573
|
+
fi
|
|
574
|
+
printf '\n'
|
|
575
|
+
fi
|
|
576
|
+
|
|
577
|
+
printf '## Recommendations\n\n'
|
|
578
|
+
if [ "$restored_count" -gt 0 ] && [ -n "$coverage_pct" ] && [ "$coverage_pct" -lt 50 ]; then
|
|
579
|
+
printf '**⚠️ Low Allowlist Coverage** — Only %d%% of changes were kept.\n' "$coverage_pct"
|
|
580
|
+
printf 'Consider:\n'
|
|
581
|
+
printf '1. Reviewing the TASK_PROMPT to be more specific about scope\n'
|
|
582
|
+
printf '2. Widening the allowlist to include related files\n'
|
|
583
|
+
printf "3. Running \`scripts/suggest-allowlist.sh\` to auto-generate a better allowlist\n\n"
|
|
584
|
+
fi
|
|
585
|
+
printf 'Run subsequent operations with an updated allowlist:\n'
|
|
586
|
+
printf '```bash\n'
|
|
587
|
+
printf 'KASEKI_CHANGED_FILES_ALLOWLIST="<your-pattern>" ./run-kaseki.sh\n'
|
|
588
|
+
printf '```\n\n'
|
|
589
|
+
printf "For help on allowlist patterns, see \`docs/QUALITY_GATES.md\`.\n"
|
|
590
|
+
} > /results/restoration-report.md
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
check_validation_allowlist() {
|
|
594
|
+
if [ -z "$KASEKI_VALIDATION_ALLOWLIST" ]; then
|
|
595
|
+
return 0
|
|
596
|
+
fi
|
|
597
|
+
if [ ! -d /workspace/repo/.git ]; then
|
|
598
|
+
return 0
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
local allowlist_regex validation_violation_count
|
|
602
|
+
allowlist_regex="$(build_allowlist_regex "$KASEKI_VALIDATION_ALLOWLIST")"
|
|
603
|
+
[ -z "$allowlist_regex" ] && return 0
|
|
604
|
+
validation_violation_count=0
|
|
605
|
+
|
|
606
|
+
while IFS= read -r changed_file || [ -n "$changed_file" ]; do
|
|
607
|
+
[ -z "$changed_file" ] && continue
|
|
608
|
+
if ! printf '%s\n' "$changed_file" | grep -Eq "^(${allowlist_regex})$"; then
|
|
609
|
+
printf 'Validation-phase file outside allowlist: %s\n' "$changed_file" | tee -a /results/quality.log
|
|
610
|
+
validation_violation_count=$((validation_violation_count + 1))
|
|
611
|
+
emit_event "quality_gate_rule_evaluated" "rule=validation_allowlist" "passed=false" "file=$changed_file"
|
|
612
|
+
else
|
|
613
|
+
emit_event "quality_gate_rule_evaluated" "rule=validation_allowlist" "passed=true" "file=$changed_file"
|
|
614
|
+
fi
|
|
615
|
+
done < /results/changed-files.txt
|
|
616
|
+
|
|
617
|
+
if [ "$validation_violation_count" -gt 0 ]; then
|
|
618
|
+
QUALITY_EXIT=7
|
|
619
|
+
QUALITY_FAILURE_REASON="validation_allowlist_check: $validation_violation_count file(s) changed during validation outside KASEKI_VALIDATION_ALLOWLIST"
|
|
620
|
+
printf '\n[validation-allowlist] %d file(s) modified during validation outside allowlist\n' "$validation_violation_count" | tee -a /results/quality.log
|
|
621
|
+
return 1
|
|
622
|
+
fi
|
|
623
|
+
return 0
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
finish() {
|
|
628
|
+
local code=$?
|
|
629
|
+
if [ "$code" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
630
|
+
STATUS="$code"
|
|
631
|
+
FAILED_COMMAND="unexpected shell failure"
|
|
632
|
+
fi
|
|
633
|
+
# Authoritative call site: this runs at EXIT so artifacts reflect final repo state.
|
|
634
|
+
collect_git_artifacts
|
|
635
|
+
|
|
636
|
+
# Debug output for restoration report generation
|
|
637
|
+
if [ -f /results/restoration.jsonl ]; then
|
|
638
|
+
printf '[debug] restoration.jsonl exists (size=%d bytes)\n' "$(wc -c < /results/restoration.jsonl)" >&2
|
|
639
|
+
else
|
|
640
|
+
printf '[debug] restoration.jsonl does not exist\n' >&2
|
|
641
|
+
fi
|
|
642
|
+
|
|
643
|
+
if ! generate_restoration_report; then
|
|
644
|
+
printf 'warning: restoration report generation failed, but continuing with cleanup\n' >&2
|
|
645
|
+
fi
|
|
646
|
+
|
|
647
|
+
# Calculate and record maturity score
|
|
648
|
+
if [ -x /app/scripts/kaseki-maturity-score.sh ]; then
|
|
649
|
+
/app/scripts/kaseki-maturity-score.sh /workspace/repo /results/maturity-score.json 2>/dev/null || true
|
|
650
|
+
fi
|
|
651
|
+
|
|
652
|
+
# Calculate and record performance metrics
|
|
653
|
+
if [ -x /app/scripts/kaseki-performance-metrics.sh ] && [ -f /results/stage-timings.tsv ]; then
|
|
654
|
+
/app/scripts/kaseki-performance-metrics.sh /results/stage-timings.tsv /results/performance-metrics.json 2>/dev/null || true
|
|
655
|
+
fi
|
|
656
|
+
|
|
657
|
+
write_result_summary
|
|
658
|
+
write_failure_json "$STATUS"
|
|
659
|
+
write_repo_memory_summary
|
|
660
|
+
write_metadata "$STATUS"
|
|
661
|
+
exit "$STATUS"
|
|
662
|
+
}
|
|
663
|
+
trap finish EXIT
|
|
664
|
+
|
|
665
|
+
run_step() {
|
|
666
|
+
local label="$1"
|
|
667
|
+
shift
|
|
668
|
+
local step_start step_end code
|
|
669
|
+
step_start="$(date +%s)"
|
|
670
|
+
set_current_stage "$label"
|
|
671
|
+
printf '\n==> %s\n' "$label"
|
|
672
|
+
emit_progress "$label" "started"
|
|
673
|
+
# Keep this explicit branch (instead of relying on `set -e`) so we can
|
|
674
|
+
# always emit progress/timing and preserve FAILED_COMMAND deterministically.
|
|
675
|
+
if "$@"; then
|
|
676
|
+
code=0
|
|
677
|
+
else
|
|
678
|
+
code=$?
|
|
679
|
+
fi
|
|
680
|
+
step_end="$(date +%s)"
|
|
681
|
+
emit_progress "$label" "finished with exit $code"
|
|
682
|
+
record_stage_timing "$label" "$code" "$((step_end - step_start))" ""
|
|
683
|
+
if [ "$code" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
684
|
+
STATUS="$code"
|
|
685
|
+
FAILED_COMMAND="$label"
|
|
686
|
+
fi
|
|
687
|
+
return "$code"
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
run_step_dry() {
|
|
691
|
+
local label="$1"
|
|
692
|
+
shift
|
|
693
|
+
local step_start step_end
|
|
694
|
+
step_start="$(date +%s)"
|
|
695
|
+
set_current_stage "$label"
|
|
696
|
+
printf '\n==> %s (DRY-RUN: simulated)\n' "$label"
|
|
697
|
+
emit_progress "$label" "started (dry-run)"
|
|
698
|
+
# Show what commands would be run without executing them
|
|
699
|
+
printf '%s\n' "$@" >> /results/validation.log
|
|
700
|
+
step_end="$(date +%s)"
|
|
701
|
+
emit_progress "$label" "finished (dry-run, simulated exit 0)"
|
|
702
|
+
record_stage_timing "$label" "0" "$((step_end - step_start))" "dry-run"
|
|
703
|
+
return 0
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
record_stage_timing() {
|
|
707
|
+
local stage="$1"
|
|
708
|
+
local exit_code="$2"
|
|
709
|
+
local duration_seconds="$3"
|
|
710
|
+
local detail="${4:-}"
|
|
711
|
+
printf '%s\t%s\t%s\t%s\n' "$stage" "$exit_code" "$duration_seconds" "$detail" >> "$STAGE_TIMINGS_FILE"
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
set_dependency_cache_status() {
|
|
715
|
+
local status="$1"
|
|
716
|
+
local detail="${2:-}"
|
|
717
|
+
printf '%s\t%s\n' "$status" "$detail" >> "$DEPENDENCY_CACHE_LOG"
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
compute_git_cache_key() {
|
|
721
|
+
local hash
|
|
722
|
+
hash="$(printf '%s' "$REPO_URL" | sha256sum | awk '{print $1}')"
|
|
723
|
+
printf 'repo-%s' "$hash"
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
is_valid_git_mirror() {
|
|
727
|
+
local mirror="$1"
|
|
728
|
+
[ -d "$mirror" ] || return 1
|
|
729
|
+
[ "$(git -C "$mirror" rev-parse --is-bare-repository 2>/dev/null || true)" = "true" ] || return 1
|
|
730
|
+
git -C "$mirror" remote get-url origin >/dev/null 2>&1 || return 1
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
run_direct_clone() {
|
|
734
|
+
rm -rf /workspace/repo
|
|
735
|
+
GIT_CLONE_STRATEGY="direct_shallow"
|
|
736
|
+
git clone --depth 1 --branch "$GIT_REF" "$REPO_URL" /workspace/repo
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
clone_with_git_cache() {
|
|
740
|
+
local cache_root="$KASEKI_GIT_CACHE_ROOT"
|
|
741
|
+
local mirror lock_file tmp_mirror lock_rc fetch_rc mirror_rc clone_rc
|
|
742
|
+
|
|
743
|
+
if [ "$KASEKI_GIT_CACHE_MODE" != "mirror" ]; then
|
|
744
|
+
GIT_CACHE_STATUS="disabled"
|
|
745
|
+
GIT_CACHE_HIT="false"
|
|
746
|
+
emit_progress "clone repository" "git cache disabled mode=$KASEKI_GIT_CACHE_MODE"
|
|
747
|
+
run_direct_clone
|
|
748
|
+
return $?
|
|
749
|
+
fi
|
|
750
|
+
|
|
751
|
+
GIT_CACHE_KEY="$(compute_git_cache_key)"
|
|
752
|
+
mirror="$cache_root/${GIT_CACHE_KEY}.git"
|
|
753
|
+
lock_file="$cache_root/${GIT_CACHE_KEY}.lock"
|
|
754
|
+
GIT_CACHE_MIRROR="$mirror"
|
|
755
|
+
|
|
756
|
+
if ! mkdir -p "$cache_root" 2>/dev/null; then
|
|
757
|
+
GIT_CACHE_STATUS="unavailable"
|
|
758
|
+
GIT_CACHE_HIT="false"
|
|
759
|
+
emit_error_event "git_cache_unavailable" "Cannot create git cache directory $cache_root; using direct clone" "fallback_direct_clone"
|
|
760
|
+
run_direct_clone
|
|
761
|
+
return $?
|
|
762
|
+
fi
|
|
763
|
+
|
|
764
|
+
exec 9>"$lock_file"
|
|
765
|
+
flock 9
|
|
766
|
+
lock_rc=$?
|
|
767
|
+
if [ "$lock_rc" -ne 0 ]; then
|
|
768
|
+
GIT_CACHE_STATUS="lock_failed"
|
|
769
|
+
GIT_CACHE_HIT="false"
|
|
770
|
+
emit_error_event "git_cache_lock_failed" "Cannot lock $lock_file; using direct clone" "fallback_direct_clone"
|
|
771
|
+
run_direct_clone
|
|
772
|
+
return $?
|
|
773
|
+
fi
|
|
774
|
+
|
|
775
|
+
if is_valid_git_mirror "$mirror"; then
|
|
776
|
+
GIT_CACHE_STATUS="hit"
|
|
777
|
+
GIT_CACHE_HIT="true"
|
|
778
|
+
emit_progress "clone repository" "git cache hit key=$GIT_CACHE_KEY mirror=$mirror"
|
|
779
|
+
timeout "$KASEKI_GIT_CACHE_FETCH_TIMEOUT_SECONDS" git -C "$mirror" fetch --prune --tags origin
|
|
780
|
+
fetch_rc=$?
|
|
781
|
+
if [ "$fetch_rc" -ne 0 ]; then
|
|
782
|
+
flock -u 9 || true
|
|
783
|
+
GIT_CACHE_STATUS="fetch_failed"
|
|
784
|
+
GIT_CACHE_HIT="true"
|
|
785
|
+
emit_error_event "git_cache_fetch_failed" "Mirror fetch failed or timed out for key=$GIT_CACHE_KEY exit=$fetch_rc; using direct clone" "fallback_direct_clone"
|
|
786
|
+
run_direct_clone
|
|
787
|
+
return $?
|
|
788
|
+
fi
|
|
789
|
+
else
|
|
790
|
+
GIT_CACHE_STATUS="miss"
|
|
791
|
+
GIT_CACHE_HIT="false"
|
|
792
|
+
emit_progress "clone repository" "git cache miss key=$GIT_CACHE_KEY mirror=$mirror"
|
|
793
|
+
if [ -e "$mirror" ]; then
|
|
794
|
+
rm -rf "$mirror"
|
|
795
|
+
fi
|
|
796
|
+
tmp_mirror="${mirror}.tmp.$$"
|
|
797
|
+
rm -rf "$tmp_mirror"
|
|
798
|
+
timeout "$KASEKI_GIT_CACHE_FETCH_TIMEOUT_SECONDS" git clone --mirror "$REPO_URL" "$tmp_mirror"
|
|
799
|
+
mirror_rc=$?
|
|
800
|
+
if [ "$mirror_rc" -eq 0 ] && is_valid_git_mirror "$tmp_mirror"; then
|
|
801
|
+
mv "$tmp_mirror" "$mirror"
|
|
802
|
+
else
|
|
803
|
+
rm -rf "$tmp_mirror"
|
|
804
|
+
flock -u 9 || true
|
|
805
|
+
GIT_CACHE_STATUS="populate_failed"
|
|
806
|
+
emit_error_event "git_cache_populate_failed" "Mirror populate failed or timed out for key=$GIT_CACHE_KEY exit=$mirror_rc; using direct clone" "fallback_direct_clone"
|
|
807
|
+
run_direct_clone
|
|
808
|
+
return $?
|
|
809
|
+
fi
|
|
810
|
+
fi
|
|
811
|
+
flock -u 9 || true
|
|
812
|
+
|
|
813
|
+
rm -rf /workspace/repo
|
|
814
|
+
GIT_CLONE_STRATEGY="reference_shallow"
|
|
815
|
+
git clone --reference-if-able "$mirror" --depth 1 --branch "$GIT_REF" "$REPO_URL" /workspace/repo
|
|
816
|
+
clone_rc=$?
|
|
817
|
+
if [ "$clone_rc" -eq 0 ]; then
|
|
818
|
+
return 0
|
|
819
|
+
fi
|
|
820
|
+
|
|
821
|
+
rm -rf /workspace/repo
|
|
822
|
+
GIT_CLONE_STRATEGY="mirror_local"
|
|
823
|
+
emit_error_event "git_cache_reference_clone_failed" "Reference clone failed for key=$GIT_CACHE_KEY exit=$clone_rc; trying local mirror clone" "try_mirror_clone"
|
|
824
|
+
git clone --branch "$GIT_REF" "$mirror" /workspace/repo
|
|
825
|
+
clone_rc=$?
|
|
826
|
+
if [ "$clone_rc" -eq 0 ] && git -C /workspace/repo rev-parse --verify HEAD >/dev/null 2>&1; then
|
|
827
|
+
git -C /workspace/repo remote set-url origin "$REPO_URL" >/dev/null 2>&1 || true
|
|
828
|
+
return 0
|
|
829
|
+
fi
|
|
830
|
+
|
|
831
|
+
rm -rf /workspace/repo
|
|
832
|
+
GIT_CACHE_STATUS="mirror_clone_failed"
|
|
833
|
+
emit_error_event "git_cache_mirror_clone_failed" "Mirror clone failed for key=$GIT_CACHE_KEY exit=$clone_rc; using direct clone" "fallback_direct_clone"
|
|
834
|
+
run_direct_clone
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
run_clone_repository() {
|
|
838
|
+
local step_start step_end code detail
|
|
839
|
+
step_start="$(date +%s)"
|
|
840
|
+
set_current_stage "clone repository"
|
|
841
|
+
printf '\n==> clone repository\n'
|
|
842
|
+
emit_progress "clone repository" "started cache_mode=$KASEKI_GIT_CACHE_MODE"
|
|
843
|
+
if clone_with_git_cache; then
|
|
844
|
+
code=0
|
|
845
|
+
else
|
|
846
|
+
code=$?
|
|
847
|
+
fi
|
|
848
|
+
step_end="$(date +%s)"
|
|
849
|
+
GIT_CLONE_DURATION_SECONDS="$((step_end - step_start))"
|
|
850
|
+
detail="cache_mode=$GIT_CACHE_MODE_USED cache_status=$GIT_CACHE_STATUS cache_hit=$GIT_CACHE_HIT cache_key=$GIT_CACHE_KEY strategy=$GIT_CLONE_STRATEGY mirror=$GIT_CACHE_MIRROR"
|
|
851
|
+
emit_progress "clone repository" "finished with exit $code elapsed=${GIT_CLONE_DURATION_SECONDS}s $detail"
|
|
852
|
+
emit_event "git_clone_cache" \
|
|
853
|
+
"mode=$GIT_CACHE_MODE_USED" \
|
|
854
|
+
"status=$GIT_CACHE_STATUS" \
|
|
855
|
+
"cache_hit=$GIT_CACHE_HIT" \
|
|
856
|
+
"cache_key=$GIT_CACHE_KEY" \
|
|
857
|
+
"strategy=$GIT_CLONE_STRATEGY" \
|
|
858
|
+
"mirror=$GIT_CACHE_MIRROR" \
|
|
859
|
+
"duration_seconds=$GIT_CLONE_DURATION_SECONDS" \
|
|
860
|
+
"exit_code=$code"
|
|
861
|
+
record_stage_timing "clone repository" "$code" "$GIT_CLONE_DURATION_SECONDS" "$detail"
|
|
862
|
+
if [ "$code" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
863
|
+
STATUS="$code"
|
|
864
|
+
FAILED_COMMAND="clone repository"
|
|
865
|
+
fi
|
|
866
|
+
return "$code"
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
same_filesystem() {
|
|
871
|
+
local left="$1"
|
|
872
|
+
local right="$2"
|
|
873
|
+
local left_device right_device
|
|
874
|
+
left_device="$(stat -c %d "$left" 2>/dev/null || true)"
|
|
875
|
+
right_device="$(stat -c %d "$right" 2>/dev/null || true)"
|
|
876
|
+
[ -n "$left_device" ] && [ "$left_device" = "$right_device" ]
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
restore_node_modules_from_cache() {
|
|
880
|
+
local source_dir="$1"
|
|
881
|
+
local target_dir="$2"
|
|
882
|
+
local mode="${3:-copy}"
|
|
883
|
+
DEPENDENCY_RESTORE_METHOD="$mode"
|
|
884
|
+
case "$mode" in
|
|
885
|
+
copy)
|
|
886
|
+
cp -a "$source_dir" "$target_dir"
|
|
887
|
+
;;
|
|
888
|
+
hardlink)
|
|
889
|
+
if same_filesystem "$source_dir" "$(dirname "$target_dir")"; then
|
|
890
|
+
if cp -al "$source_dir" "$target_dir"; then
|
|
891
|
+
DEPENDENCY_RESTORE_METHOD="hardlink"
|
|
892
|
+
return 0
|
|
893
|
+
fi
|
|
894
|
+
DEPENDENCY_RESTORE_METHOD="hardlink_fallback_copy"
|
|
895
|
+
printf 'Dependency cache status: hardlink restore failed; falling back to copy.\n' | tee -a "$DEPENDENCY_CACHE_LOG"
|
|
896
|
+
cp -a "$source_dir" "$target_dir"
|
|
897
|
+
else
|
|
898
|
+
DEPENDENCY_RESTORE_METHOD="hardlink_cross_fs_copy"
|
|
899
|
+
printf 'Dependency cache status: hardlink restore skipped because cache and workspace are on different filesystems; falling back to copy.\n' | tee -a "$DEPENDENCY_CACHE_LOG"
|
|
900
|
+
cp -a "$source_dir" "$target_dir"
|
|
901
|
+
fi
|
|
902
|
+
;;
|
|
903
|
+
symlink)
|
|
904
|
+
# Experimental: only keep this restore if downstream validation confirms tooling
|
|
905
|
+
# tolerates a symlinked node_modules tree.
|
|
906
|
+
DEPENDENCY_RESTORE_METHOD="symlink_experimental"
|
|
907
|
+
ln -s "$source_dir" "$target_dir"
|
|
908
|
+
;;
|
|
909
|
+
*)
|
|
910
|
+
printf 'Unsupported KASEKI_DEPENDENCY_RESTORE_MODE: %s (expected copy, hardlink, or symlink)\n' "$mode" >&2
|
|
911
|
+
return 2
|
|
912
|
+
;;
|
|
913
|
+
esac
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
publish_node_modules_cache() {
|
|
917
|
+
local source_dir="$1"
|
|
918
|
+
local tmp_cache_dir="$2"
|
|
919
|
+
rm -rf "$tmp_cache_dir"
|
|
920
|
+
mkdir -p "$tmp_cache_dir" && cp -a "$source_dir/." "$tmp_cache_dir/"
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
dependency_cache_flags_identity() {
|
|
924
|
+
printf 'omit_dev=%s\nignore_scripts=%s\n' "${KASEKI_NPM_OMIT_DEV:-0}" "${KASEKI_INSTALL_IGNORE_SCRIPTS:-1}"
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
dependency_cache_flags_hash() {
|
|
928
|
+
dependency_cache_flags_identity | sha256sum | awk '{print $1}'
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
append_npm_install_flags() {
|
|
932
|
+
local -n flags_ref="$1"
|
|
933
|
+
flags_ref=()
|
|
934
|
+
if [ "${KASEKI_NPM_OMIT_DEV:-0}" = "1" ]; then
|
|
935
|
+
flags_ref+=("--omit=dev")
|
|
936
|
+
fi
|
|
937
|
+
if [ "${KASEKI_INSTALL_IGNORE_SCRIPTS:-1}" = "1" ]; then
|
|
938
|
+
flags_ref+=("--ignore-scripts")
|
|
939
|
+
fi
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
render_npm_install_flags() {
|
|
943
|
+
if [ "$#" -eq 0 ]; then
|
|
944
|
+
printf 'none'
|
|
945
|
+
return 0
|
|
946
|
+
fi
|
|
947
|
+
|
|
948
|
+
local rendered=""
|
|
949
|
+
local flag
|
|
950
|
+
for flag in "$@"; do
|
|
951
|
+
if [ -n "$rendered" ]; then
|
|
952
|
+
rendered+=" "
|
|
953
|
+
fi
|
|
954
|
+
rendered+="$(printf '%q' "$flag")"
|
|
955
|
+
done
|
|
956
|
+
printf '%s' "$rendered"
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
dependency_cache_key() {
|
|
960
|
+
local lock_hash="$1"
|
|
961
|
+
local node_major="$2"
|
|
962
|
+
local flags_hash="$3"
|
|
963
|
+
printf 'npm/%s/node-%s/flags-%s' "$lock_hash" "$node_major" "$flags_hash"
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
npm_run_script_name() {
|
|
967
|
+
local command="$1"
|
|
968
|
+
local npm_run_regex='^npm[[:space:]]+run[[:space:]]+([^[:space:]-][^[:space:]-]*)($|[[:space:]])'
|
|
969
|
+
if [[ "$command" =~ $npm_run_regex ]]; then
|
|
970
|
+
printf '%s' "${BASH_REMATCH[1]}"
|
|
971
|
+
return 0
|
|
972
|
+
fi
|
|
973
|
+
return 1
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
package_json_has_npm_script() {
|
|
977
|
+
local script_name="$1"
|
|
978
|
+
[ -f package.json ] || return 1
|
|
979
|
+
node - "$script_name" <<'NODE'
|
|
980
|
+
const fs = require('fs');
|
|
981
|
+
const scriptName = process.argv[2];
|
|
982
|
+
try {
|
|
983
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
984
|
+
const scripts = pkg && typeof pkg.scripts === 'object' && pkg.scripts ? pkg.scripts : {};
|
|
985
|
+
process.exit(Object.prototype.hasOwnProperty.call(scripts, scriptName) ? 0 : 1);
|
|
986
|
+
} catch {
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
NODE
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
missing_npm_script_for_validation_command() {
|
|
993
|
+
local command="$1"
|
|
994
|
+
local script_name
|
|
995
|
+
script_name="$(npm_run_script_name "$command")" || return 1
|
|
996
|
+
package_json_has_npm_script "$script_name" && return 1
|
|
997
|
+
printf '%s' "$script_name"
|
|
998
|
+
return 0
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
record_skipped_validation_command() {
|
|
1002
|
+
local command="$1"
|
|
1003
|
+
local script_name="$2"
|
|
1004
|
+
local duration_seconds="$3"
|
|
1005
|
+
{
|
|
1006
|
+
printf '\n==> %s\n' "$command"
|
|
1007
|
+
printf 'skipped: package.json does not define npm script "%s"\n' "$script_name"
|
|
1008
|
+
} 2>&1 | tee -a /results/validation.log
|
|
1009
|
+
printf '%s\tskipped\t%s\tmissing_npm_script=%s\n' "$command" "$duration_seconds" "$script_name" >> "$VALIDATION_TIMINGS_FILE"
|
|
1010
|
+
}
|
|
1011
|
+
compute_repo_memory_key() {
|
|
1012
|
+
printf '%s\n%s' "$REPO_URL" "$GIT_REF" | sha256sum | awk '{print $1}'
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
init_repo_memory_paths() {
|
|
1016
|
+
if [ "$KASEKI_REPO_MEMORY_MODE" != "summary" ]; then
|
|
1017
|
+
REPO_MEMORY_STATUS="disabled"
|
|
1018
|
+
return 0
|
|
1019
|
+
fi
|
|
1020
|
+
REPO_MEMORY_KEY="$(compute_repo_memory_key)"
|
|
1021
|
+
REPO_MEMORY_DIR="/cache/repo-memory/$REPO_MEMORY_KEY"
|
|
1022
|
+
REPO_MEMORY_FILE="$REPO_MEMORY_DIR/summary.md"
|
|
1023
|
+
REPO_MEMORY_STATUS="enabled"
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
repo_memory_is_fresh() {
|
|
1027
|
+
local memory_file="$1"
|
|
1028
|
+
local now modified ttl_seconds age_seconds size_bytes
|
|
1029
|
+
[ -f "$memory_file" ] || return 1
|
|
1030
|
+
size_bytes="$(wc -c < "$memory_file" 2>/dev/null | tr -d ' ' || printf '0')"
|
|
1031
|
+
[ "$size_bytes" -gt 0 ] || return 1
|
|
1032
|
+
[ "$size_bytes" -le "$KASEKI_REPO_MEMORY_MAX_BYTES" ] || return 1
|
|
1033
|
+
now="$(date +%s)"
|
|
1034
|
+
modified="$(stat -c %Y "$memory_file" 2>/dev/null || printf '0')"
|
|
1035
|
+
ttl_seconds=$((KASEKI_REPO_MEMORY_TTL_DAYS * 86400))
|
|
1036
|
+
age_seconds=$((now - modified))
|
|
1037
|
+
[ "$age_seconds" -ge 0 ] && [ "$age_seconds" -le "$ttl_seconds" ]
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
read_repo_memory_section() {
|
|
1041
|
+
init_repo_memory_paths
|
|
1042
|
+
[ "$KASEKI_REPO_MEMORY_MODE" = "summary" ] || return 0
|
|
1043
|
+
if ! repo_memory_is_fresh "$REPO_MEMORY_FILE"; then
|
|
1044
|
+
REPO_MEMORY_STATUS="miss_or_expired"
|
|
1045
|
+
return 0
|
|
1046
|
+
fi
|
|
1047
|
+
REPO_MEMORY_STATUS="hit"
|
|
1048
|
+
{
|
|
1049
|
+
printf '\n\n---\nPrior repository context (opt-in cache; use only as efficiency hints, not authoritative source of truth):\n'
|
|
1050
|
+
head -c "$KASEKI_REPO_MEMORY_MAX_BYTES" "$REPO_MEMORY_FILE"
|
|
1051
|
+
printf '\n---\n'
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
write_repo_memory_summary() {
|
|
1056
|
+
[ "$KASEKI_REPO_MEMORY_MODE" = "summary" ] || return 0
|
|
1057
|
+
[ "$KASEKI_DRY_RUN" != "1" ] || return 0
|
|
1058
|
+
init_repo_memory_paths
|
|
1059
|
+
[ -n "$REPO_MEMORY_FILE" ] || return 0
|
|
1060
|
+
[ "$PI_EXIT" -eq 0 ] || return 0
|
|
1061
|
+
[ "$SECRET_SCAN_EXIT" -eq 0 ] || return 0
|
|
1062
|
+
if [ "$STATUS" -ne 0 ] && [ "$KASEKI_TASK_MODE" != "inspect" ]; then
|
|
1063
|
+
return 0
|
|
1064
|
+
fi
|
|
1065
|
+
if ! mkdir -p "$REPO_MEMORY_DIR" 2>/dev/null; then
|
|
1066
|
+
emit_error_event "repo_memory_unavailable" "Cannot create repository memory directory $REPO_MEMORY_DIR" "continue"
|
|
1067
|
+
return 0
|
|
1068
|
+
fi
|
|
1069
|
+
local updated_at
|
|
1070
|
+
REPO_MEMORY_COMMIT_SHA="$(git -C /workspace/repo rev-parse HEAD 2>/dev/null || printf 'unknown')"
|
|
1071
|
+
updated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
1072
|
+
node - "$KASEKI_REPO_MEMORY_MAX_BYTES" "$REPO_MEMORY_FILE" "$REPO_URL" "$GIT_REF" "$REPO_MEMORY_COMMIT_SHA" "$updated_at" "$KASEKI_TASK_MODE" "$STATUS" "$PI_EXIT" "$VALIDATION_EXIT" "$QUALITY_EXIT" "$SECRET_SCAN_EXIT" <<'NODE' || {
|
|
1073
|
+
const fs = require('fs');
|
|
1074
|
+
const path = require('path');
|
|
1075
|
+
const [maxBytesArg, outputFile, repoUrl, gitRef, commitSha, timestamp, taskMode, status, piExit, validationExit, qualityExit, secretScanExit] = process.argv.slice(2);
|
|
1076
|
+
const maxBytes = Math.max(1024, Number(maxBytesArg) || 8000);
|
|
1077
|
+
|
|
1078
|
+
function readFile(file, maxChars = 12000) {
|
|
1079
|
+
try {
|
|
1080
|
+
return fs.readFileSync(file, 'utf8').slice(0, maxChars);
|
|
1081
|
+
} catch {
|
|
1082
|
+
return '';
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function sanitize(text) {
|
|
1087
|
+
return String(text || '')
|
|
1088
|
+
.split(/\r?\n/)
|
|
1089
|
+
.filter((line) => !/(secret|credential|password|api[_ -]?key|token|bearer|authorization|private[_ -]?key|openrouter|task prompt|user prompt|^Task:)/i.test(line))
|
|
1090
|
+
.map((line) => line.replace(/sk-[A-Za-z0-9_-]{12,}/g, '[REDACTED_SECRET]').replace(/gh[pousr]_[A-Za-z0-9_]{12,}/g, '[REDACTED_SECRET]'))
|
|
1091
|
+
.join('\n')
|
|
1092
|
+
.trim();
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function compactLines(text, limit = 16) {
|
|
1096
|
+
const lines = sanitize(text)
|
|
1097
|
+
.split(/\r?\n/)
|
|
1098
|
+
.map((line) => line.trim())
|
|
1099
|
+
.filter(Boolean)
|
|
1100
|
+
.filter((line) => !/^Artifacts:?$/i.test(line) && !/^[-*] .*\.log( |$)/i.test(line));
|
|
1101
|
+
return lines.slice(0, limit);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function changedFiles() {
|
|
1105
|
+
return sanitize(readFile('/results/changed-files.txt', 4000))
|
|
1106
|
+
.split(/\r?\n/)
|
|
1107
|
+
.map((line) => line.trim())
|
|
1108
|
+
.filter(Boolean)
|
|
1109
|
+
.slice(0, 40);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function validationOutcomes() {
|
|
1113
|
+
const rows = sanitize(readFile('/results/validation-timings.tsv', 8000))
|
|
1114
|
+
.split(/\r?\n/)
|
|
1115
|
+
.map((line) => line.split('\t'))
|
|
1116
|
+
.filter((parts) => parts.length >= 2 && parts[0]);
|
|
1117
|
+
if (!rows.length) return ['No per-command validation timings recorded.'];
|
|
1118
|
+
return rows.slice(0, 20).map(([command, exitCode, duration]) => `${command}: exit ${exitCode}${duration ? `, ${duration}s` : ''}`);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const resultLines = compactLines(readFile('/results/result-summary.md'));
|
|
1122
|
+
const analysisLines = compactLines(readFile('/results/analysis.md'), 10);
|
|
1123
|
+
const files = changedFiles();
|
|
1124
|
+
const validations = validationOutcomes();
|
|
1125
|
+
|
|
1126
|
+
let output = `# Repository Memory Summary\n\n` +
|
|
1127
|
+
`> Opt-in efficiency cache only. Treat this as prior context hints, not authoritative source of truth; inspect the repository before relying on it.\n\n` +
|
|
1128
|
+
`- Repo URL: ${repoUrl}\n` +
|
|
1129
|
+
`- Default ref: ${gitRef}\n` +
|
|
1130
|
+
`- Commit SHA: ${commitSha}\n` +
|
|
1131
|
+
`- Updated at: ${timestamp}\n` +
|
|
1132
|
+
`- Last run mode: ${taskMode}\n` +
|
|
1133
|
+
`- Exit status: overall ${status}, agent ${piExit}, validation ${validationExit}, quality ${qualityExit}, secret scan ${secretScanExit}\n` +
|
|
1134
|
+
`\n## Last run summary\n` +
|
|
1135
|
+
(resultLines.length ? resultLines.map((line) => `- ${line.replace(/^[-*]\s*/, '')}`).join('\n') : '- No result summary available.') +
|
|
1136
|
+
`\n\n## Changed files\n` +
|
|
1137
|
+
(files.length ? files.map((file) => `- ${file}`).join('\n') : '- none') +
|
|
1138
|
+
`\n\n## Validation outcomes\n` +
|
|
1139
|
+
validations.map((line) => `- ${line}`).join('\n');
|
|
1140
|
+
|
|
1141
|
+
if (analysisLines.length) {
|
|
1142
|
+
output += `\n\n## Sanitized analysis notes\n` + analysisLines.map((line) => `- ${line.replace(/^[-*]\s*/, '')}`).join('\n');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const marker = '\n\n<!-- repo-memory-truncated -->\n';
|
|
1146
|
+
let buffer = Buffer.from(output + '\n', 'utf8');
|
|
1147
|
+
if (buffer.length > maxBytes) {
|
|
1148
|
+
buffer = Buffer.from(output.slice(0, Math.max(0, maxBytes - Buffer.byteLength(marker))) + marker, 'utf8');
|
|
1149
|
+
}
|
|
1150
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
1151
|
+
fs.writeFileSync(outputFile, buffer);
|
|
1152
|
+
NODE
|
|
1153
|
+
emit_error_event "repo_memory_write_failed" "Failed to update repository memory summary" "continue"
|
|
1154
|
+
return 0
|
|
1155
|
+
}
|
|
1156
|
+
REPO_MEMORY_STATUS="updated"
|
|
1157
|
+
emit_event "repo_memory_updated" "mode=$KASEKI_REPO_MEMORY_MODE" "repo_key=$REPO_MEMORY_KEY" "summary=$REPO_MEMORY_FILE" "max_bytes=$KASEKI_REPO_MEMORY_MAX_BYTES"
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
build_agent_prompt() {
|
|
1161
|
+
local memory_section
|
|
1162
|
+
memory_section="$(read_repo_memory_section)"
|
|
1163
|
+
if [ "$KASEKI_AGENT_GUARDRAILS" != "1" ]; then
|
|
1164
|
+
printf '%s' "$TASK_PROMPT"
|
|
1165
|
+
printf '%s' "$memory_section"
|
|
1166
|
+
return 0
|
|
1167
|
+
fi
|
|
1168
|
+
|
|
1169
|
+
cat <<EOF
|
|
1170
|
+
You are editing inside a Kaseki-managed ephemeral workspace.
|
|
1171
|
+
|
|
1172
|
+
Operational guardrails:
|
|
1173
|
+
- Do not run git add, git commit, git push, gh, hub, or create pull requests. Kaseki owns commit, push, and PR creation after validation passes.
|
|
1174
|
+
- Do not run npm install, npm ci, yarn install, pnpm install, or package-manager commands that modify lockfiles. Kaseki owns dependency setup and validation.
|
|
1175
|
+
- Keep edits limited to the requested source and test files. If a tool or command changes unrelated files, restore those unrelated files before finishing.
|
|
1176
|
+
- Do not print, inspect, or expose environment variables, secrets, credentials, API keys, or mounted secret files.
|
|
1177
|
+
|
|
1178
|
+
Task:
|
|
1179
|
+
$TASK_PROMPT
|
|
1180
|
+
$memory_section
|
|
1181
|
+
EOF
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
run_github_operations() {
|
|
1185
|
+
local app_id private_key_file owner repo feature_branch token token_data
|
|
1186
|
+
|
|
1187
|
+
# Load GitHub App credentials
|
|
1188
|
+
app_id="$(cat /run/secrets/github_app_id)" || { printf 'Failed to read app ID\n' >&2; return 7; }
|
|
1189
|
+
cat /run/secrets/github_app_client_id >/dev/null || { printf 'Failed to read client ID\n' >&2; return 7; }
|
|
1190
|
+
private_key_file="/run/secrets/github_app_private_key"
|
|
1191
|
+
|
|
1192
|
+
# Parse repo URL to extract owner and repo
|
|
1193
|
+
if [[ "$REPO_URL" =~ ^https?://github\.com/([^/]+)/([^/]+)(/|\.git)?$ ]]; then
|
|
1194
|
+
owner="${BASH_REMATCH[1]}"
|
|
1195
|
+
repo="${BASH_REMATCH[2]}"
|
|
1196
|
+
else
|
|
1197
|
+
printf -- 'Cannot parse GitHub repo URL: %s\n' "$REPO_URL" | tee -a /results/git-push.log >&2
|
|
1198
|
+
return 7
|
|
1199
|
+
fi
|
|
1200
|
+
|
|
1201
|
+
printf -- 'GitHub operations: owner=%s, repo=%s\n' "$owner" "$repo" | tee -a /results/git-push.log
|
|
1202
|
+
|
|
1203
|
+
# Set git user for commits
|
|
1204
|
+
git config user.name "GitHub App [$app_id]" || { printf 'Failed to set git user name\n' >&2; return 7; }
|
|
1205
|
+
git config user.email "${app_id}+kaseki@users.noreply.github.com" || { printf 'Failed to set git email\n' >&2; return 7; }
|
|
1206
|
+
|
|
1207
|
+
# Generate GitHub App installation token
|
|
1208
|
+
printf 'Generating GitHub App installation token...\n' | tee -a /results/git-push.log
|
|
1209
|
+
token_data="$(node /usr/local/bin/github-app-token "$app_id" "$private_key_file" "$owner" "$repo")" || {
|
|
1210
|
+
printf 'Failed to generate token\n' | tee -a /results/git-push.log >&2
|
|
1211
|
+
GITHUB_PUSH_EXIT=7
|
|
1212
|
+
return 7
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
token="$(printf '%s' "$token_data" | node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); process.stdout.write(d.token || '')" 2>/dev/null)"
|
|
1216
|
+
if [ -z "$token" ]; then
|
|
1217
|
+
printf -- 'Failed to extract token from response: %s\n' "$token_data" | tee -a /results/git-push.log >&2
|
|
1218
|
+
GITHUB_PUSH_EXIT=7
|
|
1219
|
+
return 7
|
|
1220
|
+
fi
|
|
1221
|
+
|
|
1222
|
+
printf 'Token generated successfully\n' | tee -a /results/git-push.log
|
|
1223
|
+
|
|
1224
|
+
# Create and push feature branch
|
|
1225
|
+
feature_branch="kaseki/$INSTANCE_NAME"
|
|
1226
|
+
printf -- 'Creating feature branch: %s\n' "$feature_branch" | tee -a /results/git-push.log
|
|
1227
|
+
git checkout -b "$feature_branch" || {
|
|
1228
|
+
printf 'Failed to create branch\n' | tee -a /results/git-push.log >&2
|
|
1229
|
+
GITHUB_PUSH_EXIT=7
|
|
1230
|
+
return 7
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
# Commit changes (git should already have changes from pi agent)
|
|
1234
|
+
printf 'Committing changes...\n' | tee -a /results/git-push.log
|
|
1235
|
+
if [ ! -s /results/changed-files.txt ]; then
|
|
1236
|
+
printf 'No changed files to stage\n' | tee -a /results/git-push.log >&2
|
|
1237
|
+
GITHUB_PUSH_EXIT=7
|
|
1238
|
+
return 7
|
|
1239
|
+
fi
|
|
1240
|
+
while IFS= read -r changed_file || [ -n "$changed_file" ]; do
|
|
1241
|
+
[ -z "$changed_file" ] && continue
|
|
1242
|
+
git add -- "$changed_file" || {
|
|
1243
|
+
printf -- 'Failed to stage changed file: %s\n' "$changed_file" | tee -a /results/git-push.log >&2
|
|
1244
|
+
GITHUB_PUSH_EXIT=7
|
|
1245
|
+
return 7
|
|
1246
|
+
}
|
|
1247
|
+
done < /results/changed-files.txt
|
|
1248
|
+
if ! git commit -m "Kaseki: $INSTANCE_NAME"; then
|
|
1249
|
+
printf 'No changes to commit or commit failed\n' | tee -a /results/git-push.log >&2
|
|
1250
|
+
GITHUB_PUSH_EXIT=7
|
|
1251
|
+
return 7
|
|
1252
|
+
fi
|
|
1253
|
+
|
|
1254
|
+
# Push branch
|
|
1255
|
+
printf 'Pushing branch to GitHub...\n' | tee -a /results/git-push.log
|
|
1256
|
+
local askpass_file
|
|
1257
|
+
askpass_file="$(mktemp /tmp/kaseki-github-askpass.XXXXXX)" || {
|
|
1258
|
+
printf 'Failed to create GitHub credential helper\n' | tee -a /results/git-push.log >&2
|
|
1259
|
+
GITHUB_PUSH_EXIT=8
|
|
1260
|
+
return 8
|
|
1261
|
+
}
|
|
1262
|
+
cat > "$askpass_file" <<'EOF_ASKPASS'
|
|
1263
|
+
#!/usr/bin/env bash
|
|
1264
|
+
case "$1" in
|
|
1265
|
+
*Username*) printf '%s\n' x-access-token ;;
|
|
1266
|
+
*) printf '%s\n' "$KASEKI_GITHUB_TOKEN" ;;
|
|
1267
|
+
esac
|
|
1268
|
+
EOF_ASKPASS
|
|
1269
|
+
chmod 0700 "$askpass_file"
|
|
1270
|
+
|
|
1271
|
+
if KASEKI_GITHUB_TOKEN="$token" GIT_ASKPASS="$askpass_file" GIT_TERMINAL_PROMPT=0 \
|
|
1272
|
+
git push "https://github.com/$owner/$repo.git" "$feature_branch" --force-with-lease 2>&1 | tee -a /results/git-push.log; then
|
|
1273
|
+
printf 'Branch pushed successfully\n' | tee -a /results/git-push.log
|
|
1274
|
+
else
|
|
1275
|
+
rm -f "$askpass_file"
|
|
1276
|
+
printf 'Failed to push branch\n' | tee -a /results/git-push.log >&2
|
|
1277
|
+
GITHUB_PUSH_EXIT=8
|
|
1278
|
+
return 8
|
|
1279
|
+
fi
|
|
1280
|
+
rm -f "$askpass_file"
|
|
1281
|
+
|
|
1282
|
+
if [ "$KASEKI_PUBLISH_MODE" = "branch" ]; then
|
|
1283
|
+
printf 'Publish mode branch: skipping pull request creation.\n' | tee -a /results/git-push.log
|
|
1284
|
+
GITHUB_PR_EXIT=0
|
|
1285
|
+
unset token
|
|
1286
|
+
return 0
|
|
1287
|
+
fi
|
|
1288
|
+
|
|
1289
|
+
# Create pull request
|
|
1290
|
+
printf 'Creating pull request...\n' | tee -a /results/git-push.log
|
|
1291
|
+
local pr_title pr_body pr_response pr_url
|
|
1292
|
+
pr_title="Kaseki: $INSTANCE_NAME"
|
|
1293
|
+
pr_body=$(cat <<EOF
|
|
1294
|
+
Generated by Kaseki agent (instance: $INSTANCE_NAME)
|
|
1295
|
+
|
|
1296
|
+
**Model:** $KASEKI_MODEL
|
|
1297
|
+
|
|
1298
|
+
**Duration:** $(($(date +%s) - START_EPOCH)) seconds
|
|
1299
|
+
|
|
1300
|
+
**Validation:** $([ "$VALIDATION_EXIT" -eq 0 ] && printf 'passed' || printf 'failed (exit %s)' "$VALIDATION_EXIT")
|
|
1301
|
+
|
|
1302
|
+
**Quality Checks:** $([ "$QUALITY_EXIT" -eq 0 ] && printf 'passed' || printf 'failed (exit %s)' "$QUALITY_EXIT")
|
|
1303
|
+
|
|
1304
|
+
This PR is in draft status. Please review before merging.
|
|
1305
|
+
EOF
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
pr_response=$(curl -s -X POST \
|
|
1309
|
+
-H "Authorization: token $token" \
|
|
1310
|
+
-H "Accept: application/vnd.github.v3+json" \
|
|
1311
|
+
"https://api.github.com/repos/$owner/$repo/pulls" \
|
|
1312
|
+
-d "{\"title\": $(printf '%s' "$pr_title" | node -e "console.log(JSON.stringify(require('fs').readFileSync(0, 'utf8')))"), \"body\": $(printf '%s' "$pr_body" | node -e "console.log(JSON.stringify(require('fs').readFileSync(0, 'utf8')))"), \"head\": \"$feature_branch\", \"base\": \"$GIT_REF\", \"draft\": true}" 2>&1)
|
|
1313
|
+
|
|
1314
|
+
pr_url="$(printf '%s' "$pr_response" | node -e "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); process.stdout.write(d.html_url || '')" 2>/dev/null || true)"
|
|
1315
|
+
|
|
1316
|
+
if [ -n "$pr_url" ]; then
|
|
1317
|
+
GITHUB_PR_URL="$pr_url"
|
|
1318
|
+
GITHUB_PR_EXIT=0
|
|
1319
|
+
printf 'Pull request created: %s\n' "$pr_url" | tee -a /results/git-push.log
|
|
1320
|
+
else
|
|
1321
|
+
printf 'Failed to create PR. Response: %s\n' "$pr_response" | tee -a /results/git-push.log >&2
|
|
1322
|
+
GITHUB_PR_EXIT=9
|
|
1323
|
+
fi
|
|
1324
|
+
|
|
1325
|
+
# Clean up token
|
|
1326
|
+
unset token
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
printf 'Kaseki instance: %s\n' "$INSTANCE_NAME"
|
|
1330
|
+
printf 'Repository: %s\n' "$REPO_URL"
|
|
1331
|
+
printf 'Git ref: %s\n' "$GIT_REF"
|
|
1332
|
+
printf 'Provider: %s\n' "$KASEKI_PROVIDER"
|
|
1333
|
+
printf 'Model: %s\n' "$KASEKI_MODEL"
|
|
1334
|
+
printf 'Pi version: %s\n' "$PI_VERSION"
|
|
1335
|
+
|
|
1336
|
+
openrouter_api_key=""
|
|
1337
|
+
openrouter_api_key_source=""
|
|
1338
|
+
if [ -n "${OPENROUTER_API_KEY:-}" ]; then
|
|
1339
|
+
openrouter_api_key="$OPENROUTER_API_KEY"
|
|
1340
|
+
openrouter_api_key_source="env"
|
|
1341
|
+
elif [ -r /run/secrets/openrouter_api_key ]; then
|
|
1342
|
+
secret_content="$(cat /run/secrets/openrouter_api_key)"
|
|
1343
|
+
if [ -n "$secret_content" ]; then
|
|
1344
|
+
openrouter_api_key="$secret_content"
|
|
1345
|
+
openrouter_api_key_source="secret file"
|
|
1346
|
+
fi
|
|
1347
|
+
fi
|
|
1348
|
+
unset OPENROUTER_API_KEY secret_content
|
|
1349
|
+
|
|
1350
|
+
if [ -z "$openrouter_api_key" ]; then
|
|
1351
|
+
set_current_stage "agent setup"
|
|
1352
|
+
printf 'Missing OpenRouter API key. Set OPENROUTER_API_KEY or provide /run/secrets/openrouter_api_key.\n' | tee -a /results/pi-stderr.log >&2
|
|
1353
|
+
: > "$RAW_EVENTS"
|
|
1354
|
+
PI_EXIT=2
|
|
1355
|
+
STATUS=2
|
|
1356
|
+
FAILED_COMMAND="missing OPENROUTER_API_KEY"
|
|
1357
|
+
exit 0
|
|
1358
|
+
fi
|
|
1359
|
+
|
|
1360
|
+
if ! run_clone_repository; then
|
|
1361
|
+
exit 0
|
|
1362
|
+
fi
|
|
1363
|
+
cd /workspace/repo || { STATUS=1; FAILED_COMMAND="enter repository"; exit "$STATUS"; }
|
|
1364
|
+
|
|
1365
|
+
prepare_dependencies() {
|
|
1366
|
+
if [ ! -f package.json ]; then
|
|
1367
|
+
printf 'No package.json found; skipping dependency installation.\n'
|
|
1368
|
+
return 0
|
|
1369
|
+
fi
|
|
1370
|
+
|
|
1371
|
+
local lock_source=""
|
|
1372
|
+
if [ -f package-lock.json ]; then
|
|
1373
|
+
lock_source="package-lock.json"
|
|
1374
|
+
elif [ -f npm-shrinkwrap.json ]; then
|
|
1375
|
+
lock_source="npm-shrinkwrap.json"
|
|
1376
|
+
else
|
|
1377
|
+
printf 'Dependency install requires package-lock.json or npm-shrinkwrap.json; lockfile missing.\n' >&2
|
|
1378
|
+
set_dependency_cache_status "lockfile-missing" "cache_key=none repo_url=$REPO_URL git_ref=$GIT_REF"
|
|
1379
|
+
emit_progress "dependency install" "failed lockfile missing; refusing non-deterministic install" "error"
|
|
1380
|
+
return 1
|
|
1381
|
+
fi
|
|
1382
|
+
|
|
1383
|
+
local repo_ref_key lock_hash flags_hash cache_key workspace_cache_root workspace_cache_dir image_cache_dir stamp_file metadata_file
|
|
1384
|
+
local cache_lock_file cache_lock_fd tmp_cache_dir old_cache_dir install_start install_elapsed install_flags_display cache_detail
|
|
1385
|
+
local node_major cache_reused cache_source install_mode restore_mode restore_method
|
|
1386
|
+
local -a install_flags
|
|
1387
|
+
repo_ref_key="$(printf '%s@%s' "$REPO_URL" "$GIT_REF" | sha256sum | awk '{print $1}')"
|
|
1388
|
+
lock_hash="$(sha256sum "$lock_source" | awk '{print $1}')"
|
|
1389
|
+
node_major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo "unknown")"
|
|
1390
|
+
flags_hash="$(dependency_cache_flags_hash)"
|
|
1391
|
+
cache_key="$(dependency_cache_key "$lock_hash" "$node_major" "$flags_hash")"
|
|
1392
|
+
workspace_cache_root="${KASEKI_DEPENDENCY_CACHE_DIR}/${cache_key}"
|
|
1393
|
+
workspace_cache_dir="${workspace_cache_root}/node_modules"
|
|
1394
|
+
image_cache_dir="${KASEKI_IMAGE_DEPENDENCY_CACHE_DIR}/${cache_key}/node_modules"
|
|
1395
|
+
stamp_file="${workspace_cache_root}/stamp.txt"
|
|
1396
|
+
metadata_file="${workspace_cache_root}/repo-ref-metadata.tsv"
|
|
1397
|
+
cache_lock_file="${workspace_cache_root}.lock"
|
|
1398
|
+
cache_reused="false"
|
|
1399
|
+
cache_source="none"
|
|
1400
|
+
install_mode="skipped"
|
|
1401
|
+
restore_mode="$KASEKI_DEPENDENCY_RESTORE_MODE"
|
|
1402
|
+
restore_method="$restore_mode"
|
|
1403
|
+
case "$restore_mode" in
|
|
1404
|
+
copy|hardlink|symlink) ;;
|
|
1405
|
+
*)
|
|
1406
|
+
printf 'Unsupported KASEKI_DEPENDENCY_RESTORE_MODE: %s (expected copy, hardlink, or symlink)\n' "$restore_mode" >&2
|
|
1407
|
+
set_dependency_cache_status "restore-mode-invalid" "restore_mode=$restore_mode repo_url=$REPO_URL git_ref=$GIT_REF"
|
|
1408
|
+
emit_progress "dependency install" "failed invalid restore_mode=$restore_mode" "error"
|
|
1409
|
+
return 1
|
|
1410
|
+
;;
|
|
1411
|
+
esac
|
|
1412
|
+
append_npm_install_flags install_flags
|
|
1413
|
+
install_flags_display="$(render_npm_install_flags "${install_flags[@]}")"
|
|
1414
|
+
cache_detail="lock_hash=$lock_hash cache_key=$cache_key repo_ref_key=$repo_ref_key repo_url=$REPO_URL git_ref=$GIT_REF lockfile=$lock_source node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display restore_mode=$restore_mode"
|
|
1415
|
+
|
|
1416
|
+
if ! mkdir -p "$(dirname "$workspace_cache_root")"; then
|
|
1417
|
+
return 1
|
|
1418
|
+
fi
|
|
1419
|
+
if ! exec {cache_lock_fd}>"$cache_lock_file"; then
|
|
1420
|
+
return 1
|
|
1421
|
+
fi
|
|
1422
|
+
if ! flock "$cache_lock_fd"; then
|
|
1423
|
+
exec {cache_lock_fd}>&-
|
|
1424
|
+
return 1
|
|
1425
|
+
fi
|
|
1426
|
+
|
|
1427
|
+
if ! mkdir -p "$workspace_cache_root"; then
|
|
1428
|
+
exec {cache_lock_fd}>&-
|
|
1429
|
+
return 1
|
|
1430
|
+
fi
|
|
1431
|
+
|
|
1432
|
+
if [ -d node_modules ] && [ -f "$stamp_file" ]; then
|
|
1433
|
+
if grep -qx "$lock_hash" "$stamp_file"; then
|
|
1434
|
+
printf 'Dependency cache status: using existing repo node_modules for lock hash %s (repo_ref_key=%s).\n' "$lock_hash" "$repo_ref_key"
|
|
1435
|
+
set_dependency_cache_status "existing-node-modules" "$cache_detail restore_method=none"
|
|
1436
|
+
emit_event "dependency_cache_decision" "strategy=existing_node_modules" "restore_mode=$restore_mode" "restore_method=none" "reason=lock_hash_match" "location=repo" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1437
|
+
emit_progress "dependency install" "cache hit source=repo restore_mode=$restore_mode restore_method=none lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1438
|
+
record_stage_timing "dependency install" "0" "0" "cache_hit=true cache_source=repo install_mode=skipped restore_mode=$restore_mode restore_method=none lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1439
|
+
exec {cache_lock_fd}>&-
|
|
1440
|
+
return 0
|
|
1441
|
+
fi
|
|
1442
|
+
fi
|
|
1443
|
+
|
|
1444
|
+
if [ ! -d node_modules ] && [ -d "$workspace_cache_dir" ]; then
|
|
1445
|
+
printf 'Dependency cache status: restoring node_modules from workspace cache (%s; lock_hash=%s; repo_ref_key=%s).\n' "$workspace_cache_dir" "$lock_hash" "$repo_ref_key"
|
|
1446
|
+
set_dependency_cache_status "workspace-cache-hit" "$cache_detail"
|
|
1447
|
+
emit_event "dependency_cache_decision" "strategy=workspace_cache_hit" "restore_mode=$restore_mode" "location=$workspace_cache_dir" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1448
|
+
if ! restore_node_modules_from_cache "$workspace_cache_dir" ./node_modules "$restore_mode"; then
|
|
1449
|
+
exec {cache_lock_fd}>&-
|
|
1450
|
+
return 1
|
|
1451
|
+
fi
|
|
1452
|
+
restore_method="$DEPENDENCY_RESTORE_METHOD"
|
|
1453
|
+
set_dependency_cache_status "workspace-cache-restored" "$cache_detail restore_method=$restore_method"
|
|
1454
|
+
emit_event "dependency_cache_decision" "strategy=workspace_cache_restored" "restore_mode=$restore_mode" "restore_method=$restore_method" "reason=restore_completed" "location=$workspace_cache_dir" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1455
|
+
cache_reused="true"
|
|
1456
|
+
cache_source="workspace"
|
|
1457
|
+
if ! npm ls --depth=0 >/dev/null 2>&1; then
|
|
1458
|
+
printf 'Dependency cache status: workspace cache failed npm ls validation; reinstalling.\n'
|
|
1459
|
+
set_dependency_cache_status "workspace-cache-invalid" "$cache_detail restore_method=$restore_method reason=npm_ls_failed"
|
|
1460
|
+
emit_event "dependency_cache_decision" "strategy=invalidate_workspace_cache" "restore_mode=$restore_mode" "restore_method=$restore_method" "reason=npm_ls_failed" "location=$workspace_cache_dir" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1461
|
+
rm -rf node_modules
|
|
1462
|
+
cache_reused="false"
|
|
1463
|
+
cache_source="none"
|
|
1464
|
+
fi
|
|
1465
|
+
elif [ ! -d node_modules ] && [ -d "$image_cache_dir" ]; then
|
|
1466
|
+
printf 'Dependency cache status: restoring node_modules from image cache (%s; lock_hash=%s; repo_ref_key=%s).\n' "$image_cache_dir" "$lock_hash" "$repo_ref_key"
|
|
1467
|
+
set_dependency_cache_status "image-cache-hit" "$cache_detail"
|
|
1468
|
+
emit_event "dependency_cache_decision" "strategy=image_cache_hit" "restore_mode=$restore_mode" "location=$image_cache_dir" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1469
|
+
if ! restore_node_modules_from_cache "$image_cache_dir" ./node_modules "$restore_mode"; then
|
|
1470
|
+
exec {cache_lock_fd}>&-
|
|
1471
|
+
return 1
|
|
1472
|
+
fi
|
|
1473
|
+
restore_method="$DEPENDENCY_RESTORE_METHOD"
|
|
1474
|
+
set_dependency_cache_status "image-cache-restored" "$cache_detail restore_method=$restore_method"
|
|
1475
|
+
emit_event "dependency_cache_decision" "strategy=image_cache_restored" "restore_mode=$restore_mode" "restore_method=$restore_method" "reason=restore_completed" "location=$image_cache_dir" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1476
|
+
cache_reused="true"
|
|
1477
|
+
cache_source="image"
|
|
1478
|
+
if ! npm ls --depth=0 >/dev/null 2>&1; then
|
|
1479
|
+
printf 'Dependency cache status: image cache failed npm ls validation; reinstalling.\n'
|
|
1480
|
+
set_dependency_cache_status "image-cache-invalid" "$cache_detail restore_method=$restore_method reason=npm_ls_failed"
|
|
1481
|
+
emit_event "dependency_cache_decision" "strategy=invalidate_image_cache" "restore_mode=$restore_mode" "restore_method=$restore_method" "reason=npm_ls_failed" "location=$image_cache_dir" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1482
|
+
rm -rf node_modules
|
|
1483
|
+
cache_reused="false"
|
|
1484
|
+
cache_source="none"
|
|
1485
|
+
fi
|
|
1486
|
+
fi
|
|
1487
|
+
|
|
1488
|
+
if [ ! -d node_modules ]; then
|
|
1489
|
+
printf 'Dependency cache status: cache miss for lock hash %s (repo_ref_key=%s), running install.\n' "$lock_hash" "$repo_ref_key"
|
|
1490
|
+
set_dependency_cache_status "cache-miss" "$cache_detail"
|
|
1491
|
+
emit_event "dependency_cache_decision" "strategy=fresh_install" "restore_mode=$restore_mode" "restore_method=none" "reason=no_cache_available" "location=none" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1492
|
+
emit_progress "dependency install" "started cache_hit=false restore_mode=$restore_mode restore_method=none lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1493
|
+
install_start="$(date +%s)"
|
|
1494
|
+
if ! npm ci --prefer-offline "${install_flags[@]}"; then
|
|
1495
|
+
exec {cache_lock_fd}>&-
|
|
1496
|
+
return 1
|
|
1497
|
+
fi
|
|
1498
|
+
install_elapsed="$(($(date +%s) - install_start))"
|
|
1499
|
+
install_mode="npm_ci_lockfile"
|
|
1500
|
+
emit_progress "dependency install" "finished elapsed=${install_elapsed}s cache_hit=false restore_mode=$restore_mode restore_method=none lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1501
|
+
record_stage_timing "dependency install" "0" "$install_elapsed" "cache_hit=false cache_source=none install_mode=$install_mode restore_mode=$restore_mode restore_method=none lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1502
|
+
else
|
|
1503
|
+
printf 'Dependency cache status: install skipped due to cache hit.\n'
|
|
1504
|
+
set_dependency_cache_status "install-skipped" "$cache_detail restore_method=$restore_method"
|
|
1505
|
+
emit_event "dependency_cache_decision" "strategy=skip_install" "restore_mode=$restore_mode" "restore_method=$restore_method" "reason=cache_hit" "location=local" "lock_hash=$lock_hash" "cache_key=$cache_key" "repo_ref_key=$repo_ref_key" "repo_url=$REPO_URL" "git_ref=$GIT_REF" "node_major=$node_major" "flags_hash=$flags_hash"
|
|
1506
|
+
if [ "$cache_reused" = "true" ]; then
|
|
1507
|
+
emit_progress "dependency install" "cache hit source=$cache_source restore_mode=$restore_mode restore_method=$restore_method lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1508
|
+
record_stage_timing "dependency install" "0" "0" "cache_hit=true cache_source=$cache_source install_mode=skipped restore_mode=$restore_mode restore_method=$restore_method lockfile=$lock_source lock_hash=$lock_hash repo_ref_key=$repo_ref_key node_major=$node_major flags_hash=$flags_hash flags=$install_flags_display"
|
|
1509
|
+
fi
|
|
1510
|
+
fi
|
|
1511
|
+
|
|
1512
|
+
if ! mkdir -p "$workspace_cache_root"; then
|
|
1513
|
+
exec {cache_lock_fd}>&-
|
|
1514
|
+
return 1
|
|
1515
|
+
fi
|
|
1516
|
+
tmp_cache_dir="${workspace_cache_dir}.tmp.$$"
|
|
1517
|
+
old_cache_dir="${workspace_cache_dir}.old.$$"
|
|
1518
|
+
rm -rf "$tmp_cache_dir" "$old_cache_dir"
|
|
1519
|
+
if ! publish_node_modules_cache node_modules "$tmp_cache_dir"; then
|
|
1520
|
+
exec {cache_lock_fd}>&-
|
|
1521
|
+
return 1
|
|
1522
|
+
fi
|
|
1523
|
+
# Keep this publish path single-pass and atomic to avoid cache corruption.
|
|
1524
|
+
if [ -d "$workspace_cache_dir" ] && ! mv "$workspace_cache_dir" "$old_cache_dir"; then
|
|
1525
|
+
exec {cache_lock_fd}>&-
|
|
1526
|
+
return 1
|
|
1527
|
+
fi
|
|
1528
|
+
if ! mv "$tmp_cache_dir" "$workspace_cache_dir"; then
|
|
1529
|
+
exec {cache_lock_fd}>&-
|
|
1530
|
+
return 1
|
|
1531
|
+
fi
|
|
1532
|
+
if ! rm -rf "$old_cache_dir"; then
|
|
1533
|
+
exec {cache_lock_fd}>&-
|
|
1534
|
+
return 1
|
|
1535
|
+
fi
|
|
1536
|
+
if ! printf '%s\n' "$lock_hash" > "$stamp_file"; then
|
|
1537
|
+
exec {cache_lock_fd}>&-
|
|
1538
|
+
return 1
|
|
1539
|
+
fi
|
|
1540
|
+
if ! printf 'repo_ref_key=%s repo_url=%s git_ref=%s lock_hash=%s cache_key=%s flags_hash=%s restore_mode=%s restore_method=%s\n' \
|
|
1541
|
+
"$repo_ref_key" "$REPO_URL" "$GIT_REF" "$lock_hash" "$cache_key" "$flags_hash" "$restore_mode" "$restore_method" > "$metadata_file"; then
|
|
1542
|
+
exec {cache_lock_fd}>&-
|
|
1543
|
+
return 1
|
|
1544
|
+
fi
|
|
1545
|
+
|
|
1546
|
+
exec {cache_lock_fd}>&-
|
|
1547
|
+
return 0
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
if ! run_step "prepare node dependencies" prepare_dependencies; then
|
|
1551
|
+
exit 0
|
|
1552
|
+
fi
|
|
1553
|
+
|
|
1554
|
+
printf '\n==> pi coding agent\n'
|
|
1555
|
+
set_current_stage "pi coding agent"
|
|
1556
|
+
if [ "$KASEKI_DRY_RUN" = "1" ]; then
|
|
1557
|
+
printf '🔄 DRY-RUN MODE: Skipping Pi coding agent execution\n'
|
|
1558
|
+
PI_START_EPOCH="$(date +%s)"
|
|
1559
|
+
PI_EXIT=0
|
|
1560
|
+
PI_DURATION_SECONDS=$(($(date +%s) - PI_START_EPOCH))
|
|
1561
|
+
{
|
|
1562
|
+
printf 'DRY-RUN: Pi agent would have been invoked with the following configuration:\n'
|
|
1563
|
+
printf ' Provider: %s\n' "$KASEKI_PROVIDER"
|
|
1564
|
+
printf ' Model: %s\n' "$KASEKI_MODEL"
|
|
1565
|
+
printf ' Timeout: %s seconds\n' "$KASEKI_AGENT_TIMEOUT_SECONDS"
|
|
1566
|
+
printf ' Task: %s\n' "$TASK_PROMPT"
|
|
1567
|
+
} | tee -a /results/pi-stderr.log
|
|
1568
|
+
emit_progress "pi coding agent" "skipped (dry-run)"
|
|
1569
|
+
record_stage_timing "pi coding agent" "0" "$PI_DURATION_SECONDS" "dry_run=true"
|
|
1570
|
+
else
|
|
1571
|
+
set +e
|
|
1572
|
+
printf 'OpenRouter API key source: %s\n' "$openrouter_api_key_source"
|
|
1573
|
+
export KASEKI_STREAM_PROGRESS
|
|
1574
|
+
agent_prompt="$(build_agent_prompt)"
|
|
1575
|
+
PI_START_EPOCH="$(date +%s)"
|
|
1576
|
+
OPENROUTER_API_KEY="$openrouter_api_key" \
|
|
1577
|
+
timeout --signal=SIGTERM "$KASEKI_AGENT_TIMEOUT_SECONDS" \
|
|
1578
|
+
pi --mode json --no-session --provider "$KASEKI_PROVIDER" --model "$KASEKI_MODEL" "$agent_prompt" \
|
|
1579
|
+
2> >(tee -a /results/pi-stderr.log >&2) \
|
|
1580
|
+
| tee "$RAW_EVENTS" \
|
|
1581
|
+
| kaseki-pi-progress-stream /results/progress.jsonl /results/progress.log
|
|
1582
|
+
PI_EXIT="${PIPESTATUS[0]}"
|
|
1583
|
+
unset agent_prompt
|
|
1584
|
+
PI_DURATION_SECONDS=$(($(date +%s) - PI_START_EPOCH))
|
|
1585
|
+
unset OPENROUTER_API_KEY openrouter_api_key openrouter_api_key_source
|
|
1586
|
+
set -e
|
|
1587
|
+
record_stage_timing "pi coding agent" "$PI_EXIT" "$PI_DURATION_SECONDS" "timeout_seconds=$KASEKI_AGENT_TIMEOUT_SECONDS"
|
|
1588
|
+
|
|
1589
|
+
if [ "$KASEKI_DEBUG_RAW_EVENTS" = "1" ]; then
|
|
1590
|
+
cp "$RAW_EVENTS" /results/pi-events.raw.jsonl
|
|
1591
|
+
fi
|
|
1592
|
+
|
|
1593
|
+
PI_EXTRACTION_DEPS_OK=1
|
|
1594
|
+
missing_executables=()
|
|
1595
|
+
missing_helpers=()
|
|
1596
|
+
for required_exec in kaseki-pi-event-filter kaseki-pi-progress-stream; do
|
|
1597
|
+
if ! command -v "$required_exec" >/dev/null 2>&1; then
|
|
1598
|
+
missing_executables+=("$required_exec")
|
|
1599
|
+
fi
|
|
1600
|
+
done
|
|
1601
|
+
for helper_file in /app/lib/event-aggregator.js /app/lib/timestamp-tracker.js /app/lib/progress-stream-utils.js; do
|
|
1602
|
+
if [ ! -f "$helper_file" ]; then
|
|
1603
|
+
missing_helpers+=("$helper_file")
|
|
1604
|
+
fi
|
|
1605
|
+
done
|
|
1606
|
+
if [ ${#missing_executables[@]} -gt 0 ] || [ ${#missing_helpers[@]} -gt 0 ]; then
|
|
1607
|
+
PI_EXTRACTION_DEPS_OK=0
|
|
1608
|
+
missing_execs_joined="${missing_executables[*]}"
|
|
1609
|
+
missing_helpers_joined="${missing_helpers[*]}"
|
|
1610
|
+
[ -z "$missing_execs_joined" ] && missing_execs_joined="none"
|
|
1611
|
+
[ -z "$missing_helpers_joined" ] && missing_helpers_joined="none"
|
|
1612
|
+
extraction_error=$(node -e "console.log(JSON.stringify({error:'pi_extraction_dependency_missing',missing_executables:process.argv[1],missing_helpers:process.argv[2],action:'Ensure required Pi binaries are on PATH and helper files exist in the image before running extraction'}))" "$missing_execs_joined" "$missing_helpers_joined")
|
|
1613
|
+
printf '%s
|
|
1614
|
+
' "$extraction_error" | tee -a /results/pi-stderr.log /results/quality.log >&2
|
|
1615
|
+
emit_error_event "pi_extraction_dependency_missing" "missing executables: $missing_execs_joined; missing helpers: $missing_helpers_joined; ensure Pi binaries are in PATH and /app/lib helpers are present" "abort_extraction"
|
|
1616
|
+
if [ "$STATUS" -eq 0 ]; then
|
|
1617
|
+
STATUS=87
|
|
1618
|
+
FAILED_COMMAND="pi artifact extraction dependency validation"
|
|
1619
|
+
fi
|
|
1620
|
+
cp "$RAW_EVENTS" /results/pi-events.raw.jsonl 2>/dev/null || true
|
|
1621
|
+
fi
|
|
1622
|
+
|
|
1623
|
+
FILTER_EXIT=0
|
|
1624
|
+
if [ "$PI_EXTRACTION_DEPS_OK" -eq 1 ]; then
|
|
1625
|
+
set +e
|
|
1626
|
+
kaseki-pi-event-filter "$RAW_EVENTS" /results/pi-events.jsonl /results/pi-summary.json
|
|
1627
|
+
FILTER_EXIT=$?
|
|
1628
|
+
set -e
|
|
1629
|
+
fi
|
|
1630
|
+
if [ "$FILTER_EXIT" -ne 0 ]; then
|
|
1631
|
+
printf 'pi-event-filter failed with exit %s; raw events preserved as fallback artifact\n' "$FILTER_EXIT" | tee -a /results/quality.log
|
|
1632
|
+
printf 'ERROR: kaseki-pi-event-filter failed with exit %s while exporting Pi events\n' "$FILTER_EXIT" | tee -a /results/pi-stderr.log >&2
|
|
1633
|
+
emit_error_event "pi_event_filter_failed" "kaseki-pi-event-filter exited with code $FILTER_EXIT" "continue"
|
|
1634
|
+
if [ "$STATUS" -eq 0 ]; then
|
|
1635
|
+
STATUS="$FILTER_EXIT"
|
|
1636
|
+
FAILED_COMMAND="kaseki-pi-event-filter"
|
|
1637
|
+
fi
|
|
1638
|
+
cp "$RAW_EVENTS" /results/pi-events.raw.jsonl 2>/dev/null || true
|
|
1639
|
+
fi
|
|
1640
|
+
if [ -s "$RAW_EVENTS" ] && { [ ! -s /results/pi-events.jsonl ] || [ ! -s /results/pi-summary.json ]; }; then
|
|
1641
|
+
printf 'ERROR: pi event export incomplete; raw events are non-empty but event artifacts are missing/empty\n' | tee -a /results/pi-stderr.log >&2
|
|
1642
|
+
emit_error_event "pi_event_export_incomplete" "RAW_EVENTS has data but exported artifacts are empty or missing" "continue"
|
|
1643
|
+
if [ "$STATUS" -eq 0 ]; then
|
|
1644
|
+
STATUS=86
|
|
1645
|
+
FAILED_COMMAND="pi event export incomplete"
|
|
1646
|
+
fi
|
|
1647
|
+
fi
|
|
1648
|
+
ACTUAL_MODEL="$(node -e "
|
|
1649
|
+
var fs=require('fs');
|
|
1650
|
+
function clean(v){
|
|
1651
|
+
if(v===undefined||v===null) return '';
|
|
1652
|
+
v=String(v).trim();
|
|
1653
|
+
if(!v) return '';
|
|
1654
|
+
var low=v.toLowerCase();
|
|
1655
|
+
if(low==='unknown'||low==='null') return '';
|
|
1656
|
+
return v;
|
|
1657
|
+
}
|
|
1658
|
+
function fromSummaryModels(summary){
|
|
1659
|
+
var counters=summary&&summary.counters&&summary.counters.models;
|
|
1660
|
+
if(!counters||typeof counters!=='object'||Array.isArray(counters)) return '';
|
|
1661
|
+
var entries=Object.entries(counters).filter(function(ent){
|
|
1662
|
+
return clean(ent[0]) && Number(ent[1]) > 0;
|
|
1663
|
+
});
|
|
1664
|
+
if(entries.length!==1) return '';
|
|
1665
|
+
return clean(entries[0][0]);
|
|
1666
|
+
}
|
|
1667
|
+
var m='';
|
|
1668
|
+
try{
|
|
1669
|
+
var summary=require('/results/pi-summary.json');
|
|
1670
|
+
m=clean(summary.selected_model)||clean(summary.model)||fromSummaryModels(summary);
|
|
1671
|
+
}catch{}
|
|
1672
|
+
if(!m){
|
|
1673
|
+
try{
|
|
1674
|
+
var lines=fs.readFileSync('$RAW_EVENTS','utf8').split('\n');
|
|
1675
|
+
for(var i=0;i<lines.length;i++){
|
|
1676
|
+
try{
|
|
1677
|
+
var e=JSON.parse(lines[i]);
|
|
1678
|
+
m=clean(e&&e.model);
|
|
1679
|
+
if(m) break;
|
|
1680
|
+
}catch{}
|
|
1681
|
+
}
|
|
1682
|
+
}catch{}
|
|
1683
|
+
}
|
|
1684
|
+
console.log(m||'unknown');
|
|
1685
|
+
" 2>/dev/null)"
|
|
1686
|
+
if [ "$ACTUAL_MODEL" = "unknown" ]; then
|
|
1687
|
+
emit_event "warning" "warning_type=model_attribution_missing" "detail=Unable to resolve model from pi-summary.json or raw events"
|
|
1688
|
+
fi
|
|
1689
|
+
fi
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
if [ "$KASEKI_DRY_RUN" != "1" ]; then
|
|
1694
|
+
if [ "$PI_EXIT" -eq 124 ]; then
|
|
1695
|
+
printf 'pi timeout after %ss (exit 124)\n' "$KASEKI_AGENT_TIMEOUT_SECONDS" | tee -a /results/pi-stderr.log >&2
|
|
1696
|
+
if [ "$STATUS" -eq 0 ]; then
|
|
1697
|
+
STATUS=124
|
|
1698
|
+
FAILED_COMMAND="pi coding agent timeout"
|
|
1699
|
+
emit_error_event "pi_timeout" "Coding agent exceeded timeout of $KASEKI_AGENT_TIMEOUT_SECONDS seconds" "exit"
|
|
1700
|
+
fi
|
|
1701
|
+
elif [ "$PI_EXIT" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
1702
|
+
STATUS="$PI_EXIT"
|
|
1703
|
+
FAILED_COMMAND="pi coding agent"
|
|
1704
|
+
emit_error_event "pi_agent_failed" "Coding agent exited with non-zero code: $PI_EXIT" "exit"
|
|
1705
|
+
fi
|
|
1706
|
+
fi
|
|
1707
|
+
|
|
1708
|
+
printf '\n==> collect agent diff\n'
|
|
1709
|
+
set_current_stage "collect agent diff"
|
|
1710
|
+
emit_progress "collect agent diff" "started"
|
|
1711
|
+
stage_start="$(date +%s)"
|
|
1712
|
+
collect_git_artifacts
|
|
1713
|
+
restore_disallowed_changes
|
|
1714
|
+
record_stage_timing "collect agent diff" 0 "$(($(date +%s) - stage_start))" "diff_nonempty=$DIFF_NONEMPTY"
|
|
1715
|
+
emit_progress "collect agent diff" "finished"
|
|
1716
|
+
|
|
1717
|
+
printf '\n==> quality checks\n'
|
|
1718
|
+
set_current_stage "quality checks"
|
|
1719
|
+
emit_progress "quality checks" "started"
|
|
1720
|
+
stage_start="$(date +%s)"
|
|
1721
|
+
diff_size="$(wc -c < /results/git.diff | tr -d ' ')"
|
|
1722
|
+
if [ "$diff_size" -gt "$KASEKI_MAX_DIFF_BYTES" ]; then
|
|
1723
|
+
QUALITY_EXIT=4
|
|
1724
|
+
QUALITY_FAILURE_REASON="max_diff_bytes: $diff_size bytes exceeds limit of $KASEKI_MAX_DIFF_BYTES bytes"
|
|
1725
|
+
printf 'git.diff is too large: %s bytes > %s bytes\n' "$diff_size" "$KASEKI_MAX_DIFF_BYTES" | tee -a /results/quality.log
|
|
1726
|
+
emit_event "quality_gate_rule_evaluated" "rule=max_diff_bytes" "passed=false" "actual=$diff_size" "limit=$KASEKI_MAX_DIFF_BYTES"
|
|
1727
|
+
else
|
|
1728
|
+
emit_event "quality_gate_rule_evaluated" "rule=max_diff_bytes" "passed=true" "actual=$diff_size" "limit=$KASEKI_MAX_DIFF_BYTES"
|
|
1729
|
+
fi
|
|
1730
|
+
emit_progress "quality checks" "finished with exit $QUALITY_EXIT"
|
|
1731
|
+
|
|
1732
|
+
# Build a safe regex from glob-style repo-relative allowlist patterns.
|
|
1733
|
+
allowlist_regex="$(build_allowlist_regex)"
|
|
1734
|
+
if [ -n "$allowlist_regex" ]; then
|
|
1735
|
+
while IFS= read -r changed_file || [ -n "$changed_file" ]; do
|
|
1736
|
+
[ -z "$changed_file" ] && continue
|
|
1737
|
+
if ! printf '%s\n' "$changed_file" | grep -Eq "^(${allowlist_regex})$"; then
|
|
1738
|
+
QUALITY_EXIT=5
|
|
1739
|
+
QUALITY_FAILURE_REASON="allowlist_check: file '$changed_file' not in allowlist"
|
|
1740
|
+
printf 'changed file outside allowlist: %s\n' "$changed_file" | tee -a /results/quality.log
|
|
1741
|
+
emit_event "quality_gate_rule_evaluated" "rule=allowlist_check" "passed=false" "file=$changed_file"
|
|
1742
|
+
else
|
|
1743
|
+
emit_event "quality_gate_rule_evaluated" "rule=allowlist_check" "passed=true" "file=$changed_file"
|
|
1744
|
+
fi
|
|
1745
|
+
done < /results/changed-files.txt
|
|
1746
|
+
fi
|
|
1747
|
+
|
|
1748
|
+
if [ -f package.json ] && node -e "const p=require('./package.json'); process.exit(p.scripts && (p.scripts.format || p.scripts['format:check']) ? 0 : 1)" 2>/dev/null; then
|
|
1749
|
+
format_command="$(node -e "const p=require('./package.json'); console.log(p.scripts['format:check'] ? 'npm run format:check' : 'npm run format -- --check')" 2>/dev/null)"
|
|
1750
|
+
printf '%s\n' "$format_command" >> /results/format-check-command.txt
|
|
1751
|
+
fi
|
|
1752
|
+
record_stage_timing "quality checks" "$QUALITY_EXIT" "$(($(date +%s) - stage_start))" "diff_size_bytes=$diff_size"
|
|
1753
|
+
|
|
1754
|
+
printf '\n==> validation\n'
|
|
1755
|
+
set_current_stage "validation"
|
|
1756
|
+
emit_progress "validation" "started"
|
|
1757
|
+
stage_start="$(date +%s)"
|
|
1758
|
+
if [ "$KASEKI_DRY_RUN" = "1" ]; then
|
|
1759
|
+
printf '🔄 DRY-RUN MODE: Validation commands would be executed (not running in dry-run mode):\n' | tee -a /results/validation.log
|
|
1760
|
+
IFS=';' read -r -a VALIDATION_COMMANDS <<< "$KASEKI_VALIDATION_COMMANDS"
|
|
1761
|
+
for command in "${VALIDATION_COMMANDS[@]}"; do
|
|
1762
|
+
trimmed="$(printf '%s' "$command" | sed 's/^ *//; s/ *$//')"
|
|
1763
|
+
[ -z "$trimmed" ] && continue
|
|
1764
|
+
printf ' - %s\n' "$trimmed" | tee -a /results/validation.log
|
|
1765
|
+
done
|
|
1766
|
+
VALIDATION_EXIT=0
|
|
1767
|
+
record_stage_timing "validation" "0" "$(($(date +%s) - stage_start))" "dry_run=true"
|
|
1768
|
+
elif [ -z "$KASEKI_VALIDATION_COMMANDS" ] || [ "$KASEKI_VALIDATION_COMMANDS" = "none" ]; then
|
|
1769
|
+
printf 'Validation skipped because KASEKI_VALIDATION_COMMANDS=%s.\n' "${KASEKI_VALIDATION_COMMANDS:-<empty>}" | tee -a /results/validation.log
|
|
1770
|
+
record_stage_timing "validation" 0 0 "skipped_by_config"
|
|
1771
|
+
elif [ "$QUALITY_EXIT" -ne 0 ]; then
|
|
1772
|
+
printf 'Validation skipped because quality gates failed with exit %s.\n' "$QUALITY_EXIT" | tee -a /results/validation.log
|
|
1773
|
+
VALIDATION_EXIT="$QUALITY_EXIT"
|
|
1774
|
+
if [ -z "$VALIDATION_FAILURE_REASON" ]; then
|
|
1775
|
+
VALIDATION_FAILURE_REASON="quality_gate_failed: $QUALITY_FAILURE_REASON"
|
|
1776
|
+
fi
|
|
1777
|
+
record_stage_timing "validation" "$QUALITY_EXIT" 0 "skipped_after_quality_failure"
|
|
1778
|
+
elif [ "$PI_EXIT" -ne 0 ] && [ "$KASEKI_VALIDATE_AFTER_AGENT_FAILURE" != "1" ]; then
|
|
1779
|
+
printf 'Validation skipped because pi coding agent failed with exit %s. Set KASEKI_VALIDATE_AFTER_AGENT_FAILURE=1 to run validation anyway.\n' "$PI_EXIT" | tee -a /results/validation.log
|
|
1780
|
+
record_stage_timing "validation" "$PI_EXIT" 0 "skipped_after_agent_failure"
|
|
1781
|
+
else
|
|
1782
|
+
# Checkpoint: Verify working directory exists before validation
|
|
1783
|
+
if ! [ -d /workspace/repo ]; then
|
|
1784
|
+
printf 'ERROR: Working directory /workspace/repo does not exist before validation\n' | tee -a /results/validation.log
|
|
1785
|
+
printf 'Current pwd: %s\n' "$(pwd 2>&1 || echo '<pwd failed>')" | tee -a /results/validation.log
|
|
1786
|
+
printf 'Filesystem state:\n' | tee -a /results/validation.log
|
|
1787
|
+
find /workspace -maxdepth 3 -type f 2>&1 | head -100 | tee -a /results/validation.log
|
|
1788
|
+
VALIDATION_EXIT=1
|
|
1789
|
+
VALIDATION_FAILED_COMMAND_DETAIL="Working directory /workspace/repo missing before validation"
|
|
1790
|
+
record_stage_timing "validation" "$VALIDATION_EXIT" "$(($(date +%s) - stage_start))" "directory_missing"
|
|
1791
|
+
else
|
|
1792
|
+
set +e
|
|
1793
|
+
IFS=';' read -r -a VALIDATION_COMMANDS <<< "$KASEKI_VALIDATION_COMMANDS"
|
|
1794
|
+
for command in "${VALIDATION_COMMANDS[@]}"; do
|
|
1795
|
+
trimmed="$(printf '%s' "$command" | sed 's/^ *//; s/ *$//')"
|
|
1796
|
+
[ -z "$trimmed" ] && continue
|
|
1797
|
+
validation_start="$(date +%s)"
|
|
1798
|
+
if missing_npm_script="$(missing_npm_script_for_validation_command "$trimmed")"; then
|
|
1799
|
+
validation_end="$(date +%s)"
|
|
1800
|
+
duration=$((validation_end - validation_start))
|
|
1801
|
+
record_skipped_validation_command "$trimmed" "$missing_npm_script" "$duration"
|
|
1802
|
+
emit_event "validation_command_skipped" "command=$trimmed" "reason=missing_npm_script" "script=$missing_npm_script" "duration_seconds=$duration"
|
|
1803
|
+
continue
|
|
1804
|
+
fi
|
|
1805
|
+
((VALIDATION_COMMANDS_ATTEMPTED++))
|
|
1806
|
+
emit_event "validation_command_started" "command=$trimmed"
|
|
1807
|
+
{
|
|
1808
|
+
printf '\n==> %s\n' "$trimmed"
|
|
1809
|
+
unset OPENROUTER_API_KEY
|
|
1810
|
+
# Use non-login shell (bash -c) to avoid initialization issues in --read-only containers
|
|
1811
|
+
# Login shell (bash -l) sources /etc/profile and ~/.bashrc, which can fail with getcwd()
|
|
1812
|
+
# errors when running in constrained filesystem environments (read-only root, etc.)
|
|
1813
|
+
bash -c "$trimmed"
|
|
1814
|
+
command_exit=$?
|
|
1815
|
+
printf 'exit_code=%s\n' "$command_exit"
|
|
1816
|
+
exit "$command_exit"
|
|
1817
|
+
} 2>&1 | tee -a /results/validation.log
|
|
1818
|
+
command_exit="${PIPESTATUS[0]}"
|
|
1819
|
+
validation_end="$(date +%s)"
|
|
1820
|
+
duration=$((validation_end - validation_start))
|
|
1821
|
+
printf '%s\t%s\t%s\n' "$trimmed" "$command_exit" "$duration" >> "$VALIDATION_TIMINGS_FILE"
|
|
1822
|
+
emit_event "validation_command_finished" "command=$trimmed" "exit_code=$command_exit" "duration_seconds=$duration"
|
|
1823
|
+
if [ "$command_exit" -ne 0 ] && [ "$VALIDATION_EXIT" -eq 0 ]; then
|
|
1824
|
+
VALIDATION_EXIT="$command_exit"
|
|
1825
|
+
VALIDATION_FAILED_COMMAND_DETAIL="first failing command was \"$trimmed\" with exit $command_exit"
|
|
1826
|
+
VALIDATION_FAILURE_REASON="validation_command_failed: $trimmed (exit $command_exit)"
|
|
1827
|
+
# Enhanced diagnostics for getcwd-type errors
|
|
1828
|
+
if grep -q 'getcwd\|No such file or directory\|cannot access parent directories' /results/validation.log; then
|
|
1829
|
+
{
|
|
1830
|
+
printf '\n[DIAGNOSTICS] Validation command failed with directory access error:\n'
|
|
1831
|
+
printf 'Working directory status:\n'
|
|
1832
|
+
printf ' Current pwd: %s\n' "$(pwd 2>&1 || echo '<pwd failed>')"
|
|
1833
|
+
printf ' /workspace/repo exists: %s\n' "$([ -d /workspace/repo ] && echo 'yes' || echo 'no')"
|
|
1834
|
+
if [ -L /workspace/repo/node_modules ]; then
|
|
1835
|
+
printf ' node_modules is symlink → %s\n' "$(readlink /workspace/repo/node_modules 2>&1 || echo '<readlink failed>')"
|
|
1836
|
+
fi
|
|
1837
|
+
printf 'Last 20 lines of validation log:\n'
|
|
1838
|
+
tail -20 /results/validation.log
|
|
1839
|
+
} | tee -a /results/quality.log
|
|
1840
|
+
fi
|
|
1841
|
+
# Fail-fast: if enabled, stop validation loop at first failure
|
|
1842
|
+
if [ "$KASEKI_VALIDATION_FAIL_FAST" -eq 1 ]; then
|
|
1843
|
+
VALIDATION_STOPPED_EARLY=true
|
|
1844
|
+
printf 'Validation stopped at first failure (fail-fast mode enabled).\n' | tee -a /results/validation.log
|
|
1845
|
+
break
|
|
1846
|
+
fi
|
|
1847
|
+
fi
|
|
1848
|
+
done
|
|
1849
|
+
if [ -n "$VALIDATION_FAILED_COMMAND_DETAIL" ]; then
|
|
1850
|
+
printf 'Validation failed: %s\n' "$VALIDATION_FAILED_COMMAND_DETAIL" | tee -a /results/validation.log
|
|
1851
|
+
fi
|
|
1852
|
+
set -e
|
|
1853
|
+
fi
|
|
1854
|
+
record_stage_timing "validation" "$VALIDATION_EXIT" "$(($(date +%s) - stage_start))" ""
|
|
1855
|
+
fi
|
|
1856
|
+
emit_progress "validation" "finished with exit $VALIDATION_EXIT"
|
|
1857
|
+
|
|
1858
|
+
# Check validation-phase allowlist (if configured)
|
|
1859
|
+
if [ "$VALIDATION_EXIT" -eq 0 ]; then
|
|
1860
|
+
collect_git_artifacts
|
|
1861
|
+
if ! check_validation_allowlist; then
|
|
1862
|
+
: # Exit code already set in check_validation_allowlist
|
|
1863
|
+
fi
|
|
1864
|
+
fi
|
|
1865
|
+
|
|
1866
|
+
printf '\n==> secret scan\n'
|
|
1867
|
+
set_current_stage "secret scan"
|
|
1868
|
+
emit_progress "secret scan" "started"
|
|
1869
|
+
stage_start="$(date +%s)"
|
|
1870
|
+
: > /results/secret-scan.log
|
|
1871
|
+
if [ "$KASEKI_DRY_RUN" = "1" ]; then
|
|
1872
|
+
printf '🔄 DRY-RUN MODE: Skipping secret scan (no artifacts to scan)\n' | tee -a /results/secret-scan.log
|
|
1873
|
+
SECRET_SCAN_EXIT=0
|
|
1874
|
+
record_stage_timing "secret scan" "0" "$(($(date +%s) - stage_start))" "dry_run=true"
|
|
1875
|
+
else
|
|
1876
|
+
if grep -R -n -E 'sk-or-[A-Za-z0-9_-]{20,}' /results /workspace/repo/.git /workspace/repo/src /workspace/repo/tests 2>/dev/null | grep -v '/secret-scan.log:' > /results/secret-scan.log; then
|
|
1877
|
+
SECRET_SCAN_EXIT=6
|
|
1878
|
+
fi
|
|
1879
|
+
record_stage_timing "secret scan" "$SECRET_SCAN_EXIT" "$(($(date +%s) - stage_start))" ""
|
|
1880
|
+
fi
|
|
1881
|
+
emit_progress "secret scan" "finished with exit $SECRET_SCAN_EXIT"
|
|
1882
|
+
|
|
1883
|
+
build_github_skip_reasons() {
|
|
1884
|
+
GITHUB_SKIP_REASONS=()
|
|
1885
|
+
[ "$GITHUB_APP_ENABLED" != "1" ] && GITHUB_SKIP_REASONS+=("github_app_disabled")
|
|
1886
|
+
[ "$PI_EXIT" -ne 0 ] && GITHUB_SKIP_REASONS+=("agent_failed")
|
|
1887
|
+
[ "$VALIDATION_EXIT" -ne 0 ] && GITHUB_SKIP_REASONS+=("validation_failed")
|
|
1888
|
+
[ "$QUALITY_EXIT" -ne 0 ] && GITHUB_SKIP_REASONS+=("quality_failed")
|
|
1889
|
+
[ "$SECRET_SCAN_EXIT" -ne 0 ] && GITHUB_SKIP_REASONS+=("secret_scan_failed")
|
|
1890
|
+
[ "$DIFF_NONEMPTY" != "true" ] && GITHUB_SKIP_REASONS+=("empty_diff")
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
printf '\n==> github operations\n'
|
|
1894
|
+
set_current_stage "github operations"
|
|
1895
|
+
emit_progress "github operations" "started"
|
|
1896
|
+
stage_start="$(date +%s)"
|
|
1897
|
+
: > /results/git-push.log
|
|
1898
|
+
build_github_skip_reasons
|
|
1899
|
+
if [ "${#GITHUB_SKIP_REASONS[@]}" -eq 0 ]; then
|
|
1900
|
+
if [ -r /run/secrets/github_app_id ] && [ -r /run/secrets/github_app_client_id ] && [ -r /run/secrets/github_app_private_key ]; then
|
|
1901
|
+
run_github_operations
|
|
1902
|
+
else
|
|
1903
|
+
GITHUB_SKIP_REASONS+=("github_app_secrets_missing")
|
|
1904
|
+
printf -- 'GitHub operations: skipped (reasons: %s)\n' "$(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")" | tee -a /results/git-push.log >&2
|
|
1905
|
+
emit_progress "github operations" "skipped: $(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")"
|
|
1906
|
+
GITHUB_PUSH_EXIT=7
|
|
1907
|
+
fi
|
|
1908
|
+
else
|
|
1909
|
+
printf -- 'GitHub operations: skipped (reasons: %s; agent %s, validation %s, quality %s, secret_scan %s, diff %s, github_enabled %s)\n' \
|
|
1910
|
+
"$(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")" \
|
|
1911
|
+
"$([ "$PI_EXIT" -eq 0 ] && printf 'passed' || printf 'failed')" \
|
|
1912
|
+
"$([ "$VALIDATION_EXIT" -eq 0 ] && printf 'passed' || printf 'failed')" \
|
|
1913
|
+
"$([ "$QUALITY_EXIT" -eq 0 ] && printf 'passed' || printf 'failed')" \
|
|
1914
|
+
"$([ "$SECRET_SCAN_EXIT" -eq 0 ] && printf 'passed' || printf 'failed')" \
|
|
1915
|
+
"$DIFF_NONEMPTY" \
|
|
1916
|
+
"$GITHUB_APP_ENABLED" | tee -a /results/git-push.log
|
|
1917
|
+
emit_progress "github operations" "skipped: $(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")"
|
|
1918
|
+
fi
|
|
1919
|
+
if [ "$GITHUB_APP_ENABLED" = "1" ]; then
|
|
1920
|
+
emit_progress "github operations" "finished with push exit $GITHUB_PUSH_EXIT and pr exit $GITHUB_PR_EXIT"
|
|
1921
|
+
fi
|
|
1922
|
+
record_stage_timing "github operations" "$GITHUB_PUSH_EXIT" "$(($(date +%s) - stage_start))" "pr_exit=$GITHUB_PR_EXIT enabled=$GITHUB_APP_ENABLED"
|
|
1923
|
+
|
|
1924
|
+
if [ "$VALIDATION_EXIT" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
1925
|
+
STATUS="$VALIDATION_EXIT"
|
|
1926
|
+
FAILED_COMMAND="validation"
|
|
1927
|
+
if [ -n "$VALIDATION_FAILED_COMMAND_DETAIL" ]; then
|
|
1928
|
+
emit_error_event "validation_failed" "Validation failed: $VALIDATION_FAILED_COMMAND_DETAIL" "exit"
|
|
1929
|
+
else
|
|
1930
|
+
emit_error_event "validation_failed" "Validation command exited with code $VALIDATION_EXIT" "exit"
|
|
1931
|
+
fi
|
|
1932
|
+
fi
|
|
1933
|
+
|
|
1934
|
+
if [ "$QUALITY_EXIT" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
1935
|
+
STATUS="$QUALITY_EXIT"
|
|
1936
|
+
FAILED_COMMAND="quality checks"
|
|
1937
|
+
emit_error_event "quality_gate_failed" "Quality gate rule failed (exit code $QUALITY_EXIT)" "exit"
|
|
1938
|
+
fi
|
|
1939
|
+
|
|
1940
|
+
if [ "$SECRET_SCAN_EXIT" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
1941
|
+
STATUS="$SECRET_SCAN_EXIT"
|
|
1942
|
+
FAILED_COMMAND="secret scan"
|
|
1943
|
+
emit_error_event "secret_scan_failed" "Secret scan detected potential credential leak" "exit"
|
|
1944
|
+
fi
|
|
1945
|
+
|
|
1946
|
+
if [ "$GITHUB_PUSH_EXIT" -ne 0 ] && [ "$STATUS" -eq 0 ]; then
|
|
1947
|
+
STATUS="$GITHUB_PUSH_EXIT"
|
|
1948
|
+
FAILED_COMMAND="github push"
|
|
1949
|
+
emit_error_event "github_operation_failed" "GitHub push or PR creation failed (exit code $GITHUB_PUSH_EXIT)" "exit"
|
|
1950
|
+
fi
|
|
1951
|
+
|
|
1952
|
+
if [ "$DIFF_NONEMPTY" != "true" ] &&
|
|
1953
|
+
[ "$STATUS" -eq 0 ] &&
|
|
1954
|
+
[ "$KASEKI_ALLOW_EMPTY_DIFF" != "1" ] &&
|
|
1955
|
+
[ "$KASEKI_TASK_MODE" != "inspect" ]; then
|
|
1956
|
+
STATUS=3
|
|
1957
|
+
FAILED_COMMAND="empty git diff"
|
|
1958
|
+
emit_error_event "empty_diff" "Agent produced no changes to the repository" "exit"
|
|
1959
|
+
fi
|
|
1960
|
+
|
|
1961
|
+
set_current_stage "complete"
|