@cyanautomation/kaseki-agent 1.64.2 → 1.65.2

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/kaseki-agent.sh CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # shellcheck disable=SC2086,SC2016,SC2027,SC1091
2
3
  # NOTE: This script intentionally avoids global `set -e` so each stage can
3
4
  # record status/timing artifacts before deciding whether to stop.
4
5
  set -uo pipefail
@@ -74,11 +75,11 @@ KASEKI_AUTO_LINT_CLEANUP_COMMANDS="${KASEKI_AUTO_LINT_CLEANUP_COMMANDS-npm run l
74
75
  KASEKI_SKIP_MISSING_NPM_SCRIPTS="${KASEKI_SKIP_MISSING_NPM_SCRIPTS:-1}"
75
76
  KASEKI_DEBUG_RAW_EVENTS="${KASEKI_DEBUG_RAW_EVENTS:-0}"
76
77
  KASEKI_STREAM_PROGRESS="${KASEKI_STREAM_PROGRESS:-1}"
77
- KASEKI_RESULTS_DIR="${KASEKI_RESULTS_DIR:-${KASEKI_RESULTS_DIR}}"
78
+ KASEKI_RESULTS_DIR="${KASEKI_RESULTS_DIR:-/results}"
78
79
  export KASEKI_RESULTS_DIR
79
80
  KASEKI_WORKSPACE_DIR="${KASEKI_WORKSPACE_DIR:-/workspace}"
80
81
  export KASEKI_WORKSPACE_DIR
81
- KASEKI_WORKSPACE_BASELINE_DIR="${KASEKI_WORKSPACE_BASELINE_DIR:-${KASEKI_WORKSPACE_BASELINE_DIR}}"
82
+ KASEKI_WORKSPACE_BASELINE_DIR="${KASEKI_WORKSPACE_BASELINE_DIR:-${KASEKI_WORKSPACE_DIR}/baseline}"
82
83
  export KASEKI_WORKSPACE_BASELINE_DIR
83
84
  KASEKI_APP_LIB_DIR="${KASEKI_APP_LIB_DIR:-/app/lib}"
84
85
  export KASEKI_APP_LIB_DIR
@@ -270,7 +271,7 @@ LAST_COMMAND_LOG="${KASEKI_RESULTS_DIR}/last-command.log"
270
271
  # Signal handler for graceful termination
271
272
  handle_termination() {
272
273
  local signal="$1"
273
- printf '\nReceived %s; terminating kaseki-agent...\n' "$signal" | tee -a ${KASEKI_RESULTS_DIR}/progress.log
274
+ printf '\nReceived %s; terminating kaseki-agent...\n' "$signal" | tee -a "${KASEKI_RESULTS_DIR}"/progress.log
274
275
  # Exit with standard code for signal (128 + signal_number)
275
276
  # SIGINT = 130, SIGTERM = 143
276
277
  if [ "$signal" = "SIGINT" ]; then
@@ -307,8 +308,8 @@ setup_host_logging_mirror() {
307
308
  if mkdir -p "$KASEKI_LOG_DIR" 2>/dev/null && [ -w "$KASEKI_LOG_DIR" ]; then
308
309
  stamp="$(date -u +%Y%m%dT%H%M%SZ)"
309
310
  host_log_file="$KASEKI_LOG_DIR/${base_name}-${stamp}.log"
310
- exec > >(tee -a ${KASEKI_RESULTS_DIR}/stdout.log | tee -a "$host_log_file") \
311
- 2> >(tee -a ${KASEKI_RESULTS_DIR}/stderr.log | tee -a "$host_log_file" >&2)
311
+ exec > >(tee -a "${KASEKI_RESULTS_DIR}"/stdout.log | tee -a "$host_log_file") \
312
+ 2> >(tee -a "${KASEKI_RESULTS_DIR}"/stderr.log | tee -a "$host_log_file" >&2)
312
313
  printf 'Host log mirror: %s\n' "$host_log_file"
313
314
  return 0
314
315
  fi
@@ -316,7 +317,7 @@ setup_host_logging_mirror() {
316
317
  printf 'Error: strict host logging enabled, but KASEKI_LOG_DIR is not writable: %s\n' "$KASEKI_LOG_DIR" >&2
317
318
  exit 1
318
319
  fi
319
- exec > >(tee -a ${KASEKI_RESULTS_DIR}/stdout.log) 2> >(tee -a ${KASEKI_RESULTS_DIR}/stderr.log >&2)
320
+ exec > >(tee -a "${KASEKI_RESULTS_DIR}"/stdout.log) 2> >(tee -a "${KASEKI_RESULTS_DIR}"/stderr.log >&2)
320
321
  printf 'Warning: host log mirror disabled; KASEKI_LOG_DIR is unavailable: %s (set writable KASEKI_LOG_DIR to enable mirror, or set KASEKI_STRICT_HOST_LOGGING=1 to fail fast)\n' "$KASEKI_LOG_DIR" >&2
321
322
  }
322
323
 
@@ -337,41 +338,41 @@ if ! mkdir -p "${mkdir_paths[@]}"; then
337
338
  printf 'Error: Failed to create required runtime directories.\n' >&2
338
339
  exit 1
339
340
  fi
340
- : > ${KASEKI_RESULTS_DIR}/stdout.log
341
- : > ${KASEKI_RESULTS_DIR}/stderr.log
342
- : > ${KASEKI_RESULTS_DIR}/pi-events.jsonl
343
- : > ${KASEKI_RESULTS_DIR}/pi-summary.json
344
- : > ${KASEKI_RESULTS_DIR}/scouting-events.jsonl
345
- : > ${KASEKI_RESULTS_DIR}/scouting-summary.json
346
- : > ${KASEKI_RESULTS_DIR}/scouting-validation-errors.jsonl
347
- : > ${KASEKI_RESULTS_DIR}/scouting-validation-summary.txt
348
- : > ${KASEKI_RESULTS_DIR}/goal-check-events.jsonl
349
- : > ${KASEKI_RESULTS_DIR}/goal-check-summary.json
350
- : > ${KASEKI_RESULTS_DIR}/goal-check-stderr.log
351
- : > ${KASEKI_RESULTS_DIR}/goal-check-validation-errors.jsonl
352
- : > ${KASEKI_RESULTS_DIR}/goal-check-validation-summary.txt
353
- : > ${KASEKI_RESULTS_DIR}/goal-check-attempts.jsonl
354
- : > ${KASEKI_RESULTS_DIR}/goal-check.json
355
- : > ${KASEKI_RESULTS_DIR}/run-evaluation-events.jsonl
356
- : > ${KASEKI_RESULTS_DIR}/run-evaluation-summary.json
357
- : > ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log
358
- : > ${KASEKI_RESULTS_DIR}/run-evaluation.json
341
+ : > "${KASEKI_RESULTS_DIR}"/stdout.log
342
+ : > "${KASEKI_RESULTS_DIR}"/stderr.log
343
+ : > "${KASEKI_RESULTS_DIR}"/pi-events.jsonl
344
+ : > "${KASEKI_RESULTS_DIR}"/pi-summary.json
345
+ : > "${KASEKI_RESULTS_DIR}"/scouting-events.jsonl
346
+ : > "${KASEKI_RESULTS_DIR}"/scouting-summary.json
347
+ : > "${KASEKI_RESULTS_DIR}"/scouting-validation-errors.jsonl
348
+ : > "${KASEKI_RESULTS_DIR}"/scouting-validation-summary.txt
349
+ : > "${KASEKI_RESULTS_DIR}"/goal-check-events.jsonl
350
+ : > "${KASEKI_RESULTS_DIR}"/goal-check-summary.json
351
+ : > "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log
352
+ : > "${KASEKI_RESULTS_DIR}"/goal-check-validation-errors.jsonl
353
+ : > "${KASEKI_RESULTS_DIR}"/goal-check-validation-summary.txt
354
+ : > "${KASEKI_RESULTS_DIR}"/goal-check-attempts.jsonl
355
+ : > "${KASEKI_RESULTS_DIR}"/goal-check.json
356
+ : > "${KASEKI_RESULTS_DIR}"/run-evaluation-events.jsonl
357
+ : > "${KASEKI_RESULTS_DIR}"/run-evaluation-summary.json
358
+ : > "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log
359
+ : > "${KASEKI_RESULTS_DIR}"/run-evaluation.json
359
360
  : > "$TEST_IMPACT_WARNINGS_ARTIFACT"
360
361
  : > "$EXPECTATION_MISMATCH_WARNINGS_ARTIFACT"
361
- : > ${KASEKI_RESULTS_DIR}/validation.log
362
- : > ${KASEKI_RESULTS_DIR}/pre-validation.log
362
+ : > "${KASEKI_RESULTS_DIR}"/validation.log
363
+ : > "${KASEKI_RESULTS_DIR}"/pre-validation.log
363
364
  : > "$PRE_VALIDATION_RAW_LOG"
364
365
  : > "$PRE_VALIDATION_ENV_LOG"
365
366
  : > "$AUTO_LINT_CLEANUP_LOG"
366
367
  : > "$AUTO_LINT_CLEANUP_TIMINGS_FILE"
367
- : > ${KASEKI_RESULTS_DIR}/quality.log
368
- : > ${KASEKI_RESULTS_DIR}/secret-scan.log
369
- : > ${KASEKI_RESULTS_DIR}/git-push.log
370
- : > ${KASEKI_RESULTS_DIR}/progress.log
371
- : > ${KASEKI_RESULTS_DIR}/progress.jsonl
372
- : > ${KASEKI_RESULTS_DIR}/format-check-command.txt
373
- : > ${KASEKI_RESULTS_DIR}/failure.json
374
- : > ${KASEKI_RESULTS_DIR}/result-summary.md
368
+ : > "${KASEKI_RESULTS_DIR}"/quality.log
369
+ : > "${KASEKI_RESULTS_DIR}"/secret-scan.log
370
+ : > "${KASEKI_RESULTS_DIR}"/git-push.log
371
+ : > "${KASEKI_RESULTS_DIR}"/progress.log
372
+ : > "${KASEKI_RESULTS_DIR}"/progress.jsonl
373
+ : > "${KASEKI_RESULTS_DIR}"/format-check-command.txt
374
+ : > "${KASEKI_RESULTS_DIR}"/failure.json
375
+ : > "${KASEKI_RESULTS_DIR}"/result-summary.md
375
376
  : > "$VALIDATION_TIMINGS_FILE"
376
377
  : > "$PRE_VALIDATION_TIMINGS_FILE"
377
378
  : >> "$STAGE_TIMINGS_FILE"
@@ -540,7 +541,7 @@ validate_scouting_artifact_with_node() {
540
541
  "$validation_error_file" \
541
542
  "${KASEKI_RESULTS_DIR}/scouting-validation-errors.jsonl" \
542
543
  >/dev/null \
543
- 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log
544
+ 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log
544
545
  }
545
546
 
546
547
  # Validate scouting artifact and emit structured reason code
@@ -554,7 +555,7 @@ validate_scouting_artifact() {
554
555
 
555
556
  : > "$validation_error_file"
556
557
  if [ ! -f "$candidate_artifact" ]; then
557
- if [ -f ${KASEKI_RESULTS_DIR}/filesystem-readonly-reason.txt ]; then
558
+ if [ -f "${KASEKI_RESULTS_DIR}"/filesystem-readonly-reason.txt ]; then
558
559
  reason_code="readonly_filesystem"
559
560
  reason_details="1 critical scouting validation error: scouting-candidate.json missing due to read-only filesystem"
560
561
  else
@@ -569,8 +570,8 @@ validate_scouting_artifact() {
569
570
  fi
570
571
 
571
572
  printf '%s\n' "$reason_code" > "$reason_file"
572
- printf '%s\n' "$reason_details" > ${KASEKI_RESULTS_DIR}/scouting-validation-summary.txt
573
- printf '[scouting-validation] reason=%s details=%s\n' "$reason_code" "$reason_details" | tee -a ${KASEKI_RESULTS_DIR}/scouting-stderr.log
573
+ printf '%s\n' "$reason_details" > "${KASEKI_RESULTS_DIR}"/scouting-validation-summary.txt
574
+ printf '[scouting-validation] reason=%s details=%s\n' "$reason_code" "$reason_details" | tee -a "${KASEKI_RESULTS_DIR}"/scouting-stderr.log
574
575
  rm -f "$validation_error_file" 2>/dev/null || true
575
576
  [ "$reason_code" = "valid" ]
576
577
  }
@@ -626,6 +627,7 @@ function summarize(errors) {
626
627
 
627
628
  function fail(reasonHint, errors) {
628
629
  for (const error of errors) appendValidationFailure(error);
630
+ fs.mkdirSync(path.dirname(errorLog), { recursive: true });
629
631
  fs.writeFileSync(errorLog, JSON.stringify({
630
632
  reason_hint: reasonHint,
631
633
  details: summarize(errors),
@@ -643,7 +645,7 @@ try {
643
645
  expected: "valid JSON object",
644
646
  actual: error && error.message ? String(error.message) : String(error),
645
647
  severity: "critical",
646
- suggestion: "ensure exactly one valid JSON object is written to ${KASEKI_RESULTS_DIR}/goal-check-candidate.json",
648
+ suggestion: `ensure exactly one valid JSON object is written to ${resultsDir}/goal-check-candidate.json`,
647
649
  }]);
648
650
  }
649
651
 
@@ -720,7 +722,7 @@ validate_goal_check_artifact() {
720
722
  reason_code="missing_file"
721
723
  reason_details="1 critical goal-check validation error: goal-check-candidate.json"
722
724
  # shellcheck disable=SC2016
723
- node -e 'const fs=require("node:fs"); const candidate=process.argv[1]; const attempt=Number(process.argv[2]); const error={timestamp:new Date().toISOString(),attempt,field:"goal-check-candidate.json",expected:"file at ${KASEKI_RESULTS_DIR}/goal-check-candidate.json",actual:`missing: ${candidate}`,severity:"critical",suggestion:"ensure the goal-check Pi writes exactly one valid JSON object to ${KASEKI_RESULTS_DIR}/goal-check-candidate.json before exiting successfully"}; fs.appendFileSync(process.env.KASEKI_RESULTS_DIR + "/goal-check-validation-errors.jsonl", JSON.stringify(error)+"\n");' "$candidate_artifact" "$attempt" 2>> ${KASEKI_RESULTS_DIR}/goal-check-stderr.log || true
725
+ node -e 'const fs=require("node:fs"); const candidate=process.argv[1]; const attempt=Number(process.argv[2]); const error={timestamp:new Date().toISOString(),attempt,field:"goal-check-candidate.json",expected:"file at "${KASEKI_RESULTS_DIR}"/goal-check-candidate.json",actual:`missing: ${candidate}`,severity:"critical",suggestion:"ensure the goal-check Pi writes exactly one valid JSON object to ${KASEKI_RESULTS_DIR}/goal-check-candidate.json before exiting successfully"}; fs.appendFileSync(process.env.KASEKI_RESULTS_DIR + "/goal-check-validation-errors.jsonl", JSON.stringify(error)+"\n");' "$candidate_artifact" "$attempt" 2>> "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log || true
724
726
  elif ! validate_goal_check_artifact_with_node "$candidate_artifact" "$final_artifact" "$attempt" "$validation_error_file"; then
725
727
  reason_code="$(node -e 'try{const v=JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")); const hint=String(v.reason_hint||""); process.stdout.write(hint === "malformed_json" ? "malformed_json" : "schema_mismatch");}catch{process.stdout.write("schema_mismatch");}' "$validation_error_file" 2>/dev/null || printf 'schema_mismatch')"
726
728
  reason_details="$(node -e 'try{const v=JSON.parse(require("node:fs").readFileSync(process.argv[1],"utf8")); process.stdout.write(String(v.details||"goal-check artifact validation failed"));}catch{process.stdout.write("goal-check artifact validation failed");}' "$validation_error_file" 2>/dev/null || printf 'goal-check artifact validation failed')"
@@ -728,7 +730,7 @@ validate_goal_check_artifact() {
728
730
 
729
731
  printf '%s\n' "$reason_code" > "$reason_file"
730
732
  printf '%s\n' "$reason_details" > "$summary_file"
731
- printf '[goal-check-validation] reason=%s details=%s\n' "$reason_code" "$reason_details" | tee -a ${KASEKI_RESULTS_DIR}/goal-check-stderr.log
733
+ printf '[goal-check-validation] reason=%s details=%s\n' "$reason_code" "$reason_details" | tee -a "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log
732
734
  rm -f "$validation_error_file" 2>/dev/null || true
733
735
  [ "$reason_code" = "valid" ]
734
736
  }
@@ -737,20 +739,20 @@ emit_progress() {
737
739
  local stage="$1"
738
740
  local detail="$2"
739
741
  local status="${3:-info}"
740
- append_jsonl_object ${KASEKI_RESULTS_DIR}/progress.jsonl \
742
+ append_jsonl_object "${KASEKI_RESULTS_DIR}"/progress.jsonl \
741
743
  "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
742
744
  "component=kaseki-agent" \
743
745
  "stage=$stage" \
744
746
  "status=$status" \
745
747
  "instance=$INSTANCE_NAME" \
746
748
  "detail=$detail"
747
- printf '[progress] %s %s: %s\n' "$stage" "$status" "$detail" | tee -a ${KASEKI_RESULTS_DIR}/progress.log
749
+ printf '[progress] %s %s: %s\n' "$stage" "$status" "$detail" | tee -a "${KASEKI_RESULTS_DIR}"/progress.log
748
750
  }
749
751
 
750
752
  emit_event() {
751
753
  local event_type="$1"
752
754
  shift
753
- append_jsonl_object ${KASEKI_RESULTS_DIR}/progress.jsonl \
755
+ append_jsonl_object "${KASEKI_RESULTS_DIR}"/progress.jsonl \
754
756
  "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
755
757
  "component=kaseki-agent" \
756
758
  "event_type=$event_type" \
@@ -763,7 +765,7 @@ emit_error_event() {
763
765
  local detail="$2"
764
766
  local recovery="${3:-continue}"
765
767
  emit_event "error" "error_type=$error_type" "detail=$detail" "recovery_action=$recovery"
766
- printf '[error] %s: %s (recovery: %s)\n' "$error_type" "$detail" "$recovery" | tee -a ${KASEKI_RESULTS_DIR}/progress.log
768
+ printf '[error] %s: %s (recovery: %s)\n' "$error_type" "$detail" "$recovery" | tee -a "${KASEKI_RESULTS_DIR}"/progress.log
767
769
  }
768
770
 
769
771
  write_metadata() {
@@ -781,7 +783,7 @@ write_metadata() {
781
783
  stages_json="[\"unknown\"]"
782
784
  fi
783
785
 
784
- cat > ${KASEKI_RESULTS_DIR}/metadata.json <<META
786
+ cat > "${KASEKI_RESULTS_DIR}"/metadata.json <<META
785
787
  {
786
788
  "instance": $(printf '%s' "$INSTANCE_NAME" | json_encode),
787
789
  "repo_url": $(printf '%s' "$REPO_URL" | json_encode),
@@ -902,7 +904,7 @@ write_metadata() {
902
904
  "stages": $stages_json
903
905
  }
904
906
  META
905
- printf '%s\n' "$exit_code" > ${KASEKI_RESULTS_DIR}/exit_code
907
+ printf '%s\n' "$exit_code" > "${KASEKI_RESULTS_DIR}"/exit_code
906
908
  }
907
909
 
908
910
  set_current_stage() {
@@ -957,7 +959,7 @@ build_stages_array() {
957
959
 
958
960
  write_result_summary() {
959
961
  local changed_files changed_files_markdown validation_status pr_status github_skip_reasons_summary goal_check_status
960
- changed_files="$(cat ${KASEKI_RESULTS_DIR}/changed-files.txt 2>/dev/null || true)"
962
+ changed_files="$(cat "${KASEKI_RESULTS_DIR}"/changed-files.txt 2>/dev/null || true)"
961
963
  if [ -n "$changed_files" ]; then
962
964
  changed_files_markdown="$(printf '%s\n' "$changed_files" | sed 's/^/ - /')"
963
965
  else
@@ -1010,7 +1012,7 @@ write_result_summary() {
1010
1012
  goal_check_status="not reached"
1011
1013
  fi
1012
1014
 
1013
- cat > ${KASEKI_RESULTS_DIR}/result-summary.md <<SUMMARY
1015
+ cat > "${KASEKI_RESULTS_DIR}"/result-summary.md <<SUMMARY
1014
1016
  # Kaseki Result: $INSTANCE_NAME
1015
1017
 
1016
1018
  - Status: $(if [ "$STATUS" -eq 0 ]; then printf 'passed'; else printf 'failed'; fi)
@@ -1035,7 +1037,7 @@ $(if [ -n "$VALIDATION_ALLOWLIST_FAILURE_REASON" ]; then printf ' - Allowlist r
1035
1037
  $(if [ "$KASEKI_VALIDATION_RUN_ALL_COMMANDS" -eq 1 ]; then printf -- '- **ℹ️ Validation mode: Comprehensive** (KASEKI_VALIDATION_RUN_ALL_COMMANDS=1) - all %d commands executed\n' "$(echo "$KASEKI_VALIDATION_COMMANDS" | tr ';' '\n' | grep -c .)"; elif [ "$VALIDATION_STOPPED_EARLY" = "true" ]; then printf -- '- **⚠️ Validation stopped early** (fail-fast mode): %s of %s commands ran\n' "$VALIDATION_COMMANDS_ATTEMPTED" "$(echo "$KASEKI_VALIDATION_COMMANDS" | tr ';' '\n' | grep -c .)"; fi)
1036
1038
  - Test failure analysis: $TEST_FAILURE_CLASSIFICATION_STATUS
1037
1039
  $(if [ "$TEST_FAILURE_CLASSIFICATION_STATUS" = "completed" ] && [ "$NEWLY_INTRODUCED_FAILURES_COUNT" -gt 0 ]; then printf ' - ⚠️ **Newly introduced failures: %d**\n' "$NEWLY_INTRODUCED_FAILURES_COUNT"; fi)
1038
- $(if [ "$TEST_FAILURE_CLASSIFICATION_STATUS" = "completed" ] && [ -f ${KASEKI_RESULTS_DIR}/test-baseline-comparison.json ]; then printf ' - See test-baseline-comparison.json for full breakdown\n'; fi)
1040
+ $(if [ "$TEST_FAILURE_CLASSIFICATION_STATUS" = "completed" ] && [ -f "${KASEKI_RESULTS_DIR}"/test-baseline-comparison.json ]; then printf ' - See test-baseline-comparison.json for full breakdown\n'; fi)
1039
1041
  - Quality checks: $QUALITY_EXIT
1040
1042
  - Secret scan: $SECRET_SCAN_EXIT
1041
1043
  - GitHub PR: $pr_status
@@ -1077,12 +1079,12 @@ SUMMARY
1077
1079
  write_failure_json() {
1078
1080
  local exit_code="$1"
1079
1081
  local stderr_tail
1080
- stderr_tail="$(tail -20 ${KASEKI_RESULTS_DIR}/stderr.log 2>/dev/null || true)"
1082
+ stderr_tail="$(tail -20 "${KASEKI_RESULTS_DIR}"/stderr.log 2>/dev/null || true)"
1081
1083
  if [ "$exit_code" -eq 0 ]; then
1082
- : > ${KASEKI_RESULTS_DIR}/failure.json
1084
+ : > "${KASEKI_RESULTS_DIR}"/failure.json
1083
1085
  return 0
1084
1086
  fi
1085
- cat > ${KASEKI_RESULTS_DIR}/failure.json <<FAILURE
1087
+ cat > "${KASEKI_RESULTS_DIR}"/failure.json <<FAILURE
1086
1088
  {
1087
1089
  "instance": $(printf '%s' "$INSTANCE_NAME" | json_encode),
1088
1090
  "exit_code": $exit_code,
@@ -1112,21 +1114,21 @@ FAILURE
1112
1114
 
1113
1115
  collect_git_artifacts() {
1114
1116
  DIFF_NONEMPTY=false
1115
- if [ -d ${KASEKI_WORKSPACE_DIR}/repo/.git ]; then
1117
+ if [ -d "${KASEKI_WORKSPACE_DIR}"/repo/.git ]; then
1116
1118
  while IFS= read -r untracked_file || [ -n "$untracked_file" ]; do
1117
1119
  [ -z "$untracked_file" ] && continue
1118
- git -C ${KASEKI_WORKSPACE_DIR}/repo add -N -- "$untracked_file" 2>/dev/null || true
1119
- done < <(git -C ${KASEKI_WORKSPACE_DIR}/repo ls-files --others --exclude-standard 2>/dev/null || true)
1120
- git -C ${KASEKI_WORKSPACE_DIR}/repo status --short > ${KASEKI_RESULTS_DIR}/git.status 2>/dev/null || true
1121
- git -C ${KASEKI_WORKSPACE_DIR}/repo diff -- . > ${KASEKI_RESULTS_DIR}/git.diff 2>/dev/null || true
1122
- git -C ${KASEKI_WORKSPACE_DIR}/repo diff --name-only -- . > ${KASEKI_RESULTS_DIR}/changed-files.txt 2>/dev/null || true
1123
- if [ -s ${KASEKI_RESULTS_DIR}/git.diff ]; then
1120
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo add -N -- "$untracked_file" 2>/dev/null || true
1121
+ done < <(git -C "${KASEKI_WORKSPACE_DIR}"/repo ls-files --others --exclude-standard 2>/dev/null || true)
1122
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo status --short > "${KASEKI_RESULTS_DIR}"/git.status 2>/dev/null || true
1123
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo diff -- . > "${KASEKI_RESULTS_DIR}"/git.diff 2>/dev/null || true
1124
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo diff --name-only -- . > "${KASEKI_RESULTS_DIR}"/changed-files.txt 2>/dev/null || true
1125
+ if [ -s "${KASEKI_RESULTS_DIR}"/git.diff ]; then
1124
1126
  DIFF_NONEMPTY=true
1125
1127
  fi
1126
1128
  else
1127
- : > ${KASEKI_RESULTS_DIR}/git.status
1128
- : > ${KASEKI_RESULTS_DIR}/git.diff
1129
- : > ${KASEKI_RESULTS_DIR}/changed-files.txt
1129
+ : > "${KASEKI_RESULTS_DIR}"/git.status
1130
+ : > "${KASEKI_RESULTS_DIR}"/git.diff
1131
+ : > "${KASEKI_RESULTS_DIR}"/changed-files.txt
1130
1132
  fi
1131
1133
  }
1132
1134
 
@@ -1175,7 +1177,7 @@ run_static_test_impact_check() {
1175
1177
  "warning_type=test_impact_without_tests" \
1176
1178
  "artifact=$artifact" \
1177
1179
  "detail=$warning_detail"
1178
- printf '[warning] test-impact: %s (artifact: %s)\n' "$warning_detail" "$artifact" | tee -a ${KASEKI_RESULTS_DIR}/progress.log
1180
+ printf '[warning] test-impact: %s (artifact: %s)\n' "$warning_detail" "$artifact" | tee -a "${KASEKI_RESULTS_DIR}"/progress.log
1179
1181
  return 0
1180
1182
  }
1181
1183
 
@@ -1188,21 +1190,22 @@ run_expectation_mismatch_detector() {
1188
1190
  fi
1189
1191
 
1190
1192
  : > "$EXPECTATION_MISMATCH_WARNINGS_ARTIFACT"
1191
- if [ ! -s ${KASEKI_RESULTS_DIR}/git.diff ]; then
1192
- printf '[expectation-mismatch] skipped: ${KASEKI_RESULTS_DIR}/git.diff is empty\n' >> ${KASEKI_RESULTS_DIR}/progress.log
1193
+ if [ ! -s "${KASEKI_RESULTS_DIR}"/git.diff ]; then
1194
+ # shellcheck disable=SC2086
1195
+ printf '[expectation-mismatch] skipped: "${KASEKI_RESULTS_DIR}"/git.diff is empty\n' >> ${KASEKI_RESULTS_DIR}/progress.log
1193
1196
  return 0
1194
1197
  fi
1195
1198
  if [ ! -f "$detector_script" ]; then
1196
- printf '[expectation-mismatch] skipped: detector script not found (%s)\n' "$detector_script" | tee -a ${KASEKI_RESULTS_DIR}/progress.log
1199
+ printf '[expectation-mismatch] skipped: detector script not found (%s)\n' "$detector_script" | tee -a "${KASEKI_RESULTS_DIR}"/progress.log
1197
1200
  return 0
1198
1201
  fi
1199
1202
 
1200
1203
  if ! node "$detector_script" \
1201
- --repo ${KASEKI_WORKSPACE_DIR}/repo \
1202
- --diff ${KASEKI_RESULTS_DIR}/git.diff \
1204
+ --repo "${KASEKI_WORKSPACE_DIR}"/repo \
1205
+ --diff "${KASEKI_RESULTS_DIR}"/git.diff \
1203
1206
  --output "$EXPECTATION_MISMATCH_WARNINGS_ARTIFACT" \
1204
- --progress ${KASEKI_RESULTS_DIR}/progress.log; then
1205
- printf '[expectation-mismatch] warning: detector failed; continuing to validation\n' | tee -a ${KASEKI_RESULTS_DIR}/progress.log
1207
+ --progress "${KASEKI_RESULTS_DIR}"/progress.log; then
1208
+ printf '[expectation-mismatch] warning: detector failed; continuing to validation\n' | tee -a "${KASEKI_RESULTS_DIR}"/progress.log
1206
1209
  fi
1207
1210
  }
1208
1211
 
@@ -1313,7 +1316,7 @@ run_scouting_allowlist_coverage() {
1313
1316
  local scouting_artifact agent_patterns validation_patterns
1314
1317
  scouting_artifact="${1:?missing scouting artifact path}"
1315
1318
 
1316
- if [ ! -f "$scouting_artifact" ] || [ ! -f ${KASEKI_RESULTS_DIR}/changed-files.txt ]; then
1319
+ if [ ! -f "$scouting_artifact" ] || [ ! -f "${KASEKI_RESULTS_DIR}"/changed-files.txt ]; then
1317
1320
  return 0
1318
1321
  fi
1319
1322
 
@@ -1374,12 +1377,12 @@ run_scouting_allowlist_coverage() {
1374
1377
  if [ -n "$validation_warnings" ]; then
1375
1378
  printf ' ⚠ validation_phase warning: %s\n' "$validation_warnings"
1376
1379
  fi
1377
- } | tee -a ${KASEKI_RESULTS_DIR}/scouting-report.md >> ${KASEKI_RESULTS_DIR}/quality.log
1380
+ } | tee -a "${KASEKI_RESULTS_DIR}"/scouting-report.md >> "${KASEKI_RESULTS_DIR}"/quality.log
1378
1381
  fi
1379
1382
  }
1380
1383
 
1381
1384
  restore_disallowed_changes() {
1382
- if [ "$KASEKI_RESTORE_DISALLOWED_CHANGES" != "1" ] || [ ! -d ${KASEKI_WORKSPACE_DIR}/repo/.git ]; then
1385
+ if [ "$KASEKI_RESTORE_DISALLOWED_CHANGES" != "1" ] || [ ! -d "${KASEKI_WORKSPACE_DIR}"/repo/.git ]; then
1383
1386
  return 0
1384
1387
  fi
1385
1388
 
@@ -1392,7 +1395,7 @@ restore_disallowed_changes() {
1392
1395
  coverage=0
1393
1396
 
1394
1397
  # Initialize restoration tracking file
1395
- : > ${KASEKI_RESULTS_DIR}/restoration.jsonl
1398
+ : > "${KASEKI_RESULTS_DIR}"/restoration.jsonl
1396
1399
 
1397
1400
  while IFS= read -r changed_file || [ -n "$changed_file" ]; do
1398
1401
  [ -z "$changed_file" ] && continue
@@ -1402,21 +1405,21 @@ restore_disallowed_changes() {
1402
1405
  {
1403
1406
  printf '{"timestamp":"%s","event":"file_evaluated","file":"%s","status":"kept","reason":"matched_allowlist"}\n' \
1404
1407
  "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(printf '%s' "$changed_file" | sed 's/"/\\"/g')"
1405
- } >> ${KASEKI_RESULTS_DIR}/restoration.jsonl
1408
+ } >> "${KASEKI_RESULTS_DIR}"/restoration.jsonl
1406
1409
  continue
1407
1410
  fi
1408
1411
  # File did not match allowlist - restore it
1409
1412
  restored_count=$((restored_count + 1))
1410
- printf -- 'Restoring changed file outside allowlist before validation: %s\n' "$changed_file" | tee -a ${KASEKI_RESULTS_DIR}/quality.log
1413
+ printf -- 'Restoring changed file outside allowlist before validation: %s\n' "$changed_file" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
1411
1414
  emit_event "quality_gate_rule_evaluated" "rule=allowlist_restore" "passed=true" "file=$changed_file"
1412
1415
  {
1413
1416
  printf '{"timestamp":"%s","event":"file_restored","file":"%s","status":"restored","reason":"not_in_allowlist"}\n' \
1414
1417
  "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(printf '%s' "$changed_file" | sed 's/"/\\"/g')"
1415
- } >> ${KASEKI_RESULTS_DIR}/restoration.jsonl
1416
- git -C ${KASEKI_WORKSPACE_DIR}/repo restore --staged --worktree -- "$changed_file" 2>/dev/null || true
1417
- git -C ${KASEKI_WORKSPACE_DIR}/repo clean -f -- "$changed_file" 2>/dev/null || true
1418
+ } >> "${KASEKI_RESULTS_DIR}"/restoration.jsonl
1419
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo restore --staged --worktree -- "$changed_file" 2>/dev/null || true
1420
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo clean -f -- "$changed_file" 2>/dev/null || true
1418
1421
  restored_any=1
1419
- done < ${KASEKI_RESULTS_DIR}/changed-files.txt
1422
+ done < "${KASEKI_RESULTS_DIR}"/changed-files.txt
1420
1423
 
1421
1424
  # Emit restoration summary to quality.log with actionable guidance
1422
1425
  if [ $((restored_count + kept_count)) -gt 0 ]; then
@@ -1432,7 +1435,7 @@ restore_disallowed_changes() {
1432
1435
  printf ' 3. Update KASEKI_CHANGED_FILES_ALLOWLIST and re-run\n'
1433
1436
  printf 'See docs/QUALITY_GATES.md for more guidance.\n'
1434
1437
  fi
1435
- } | tee -a ${KASEKI_RESULTS_DIR}/quality.log
1438
+ } | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
1436
1439
  emit_event "allowlist_restoration_complete" "restored=$restored_count" "kept=$kept_count" "coverage=$coverage"
1437
1440
  fi
1438
1441
 
@@ -1442,7 +1445,7 @@ restore_disallowed_changes() {
1442
1445
  }
1443
1446
 
1444
1447
  generate_restoration_report() {
1445
- if [ ! -f ${KASEKI_RESULTS_DIR}/restoration.jsonl ]; then
1448
+ if [ ! -f "${KASEKI_RESULTS_DIR}"/restoration.jsonl ]; then
1446
1449
  printf '[debug] restoration report: skipping - restoration.jsonl not found\n' >&2
1447
1450
  return 0
1448
1451
  fi
@@ -1451,7 +1454,7 @@ generate_restoration_report() {
1451
1454
 
1452
1455
  # Safely extract counts from restoration.jsonl with validation
1453
1456
  printf '[debug] restoration report: extracting counts from restoration.jsonl\n' >&2
1454
- restored_count=$(grep -c '"status":"restored"' ${KASEKI_RESULTS_DIR}/restoration.jsonl 2>/dev/null || true)
1457
+ restored_count=$(grep -c '"status":"restored"' "${KASEKI_RESULTS_DIR}"/restoration.jsonl 2>/dev/null || true)
1455
1458
  restored_count=${restored_count:-0}
1456
1459
  printf '[debug] restoration report: restored_count="%s"\n' "$restored_count" >&2
1457
1460
  if ! validate_numeric "restored_count" "$restored_count"; then
@@ -1459,7 +1462,7 @@ generate_restoration_report() {
1459
1462
  return 1
1460
1463
  fi
1461
1464
 
1462
- kept_count=$(grep -c '"status":"kept"' ${KASEKI_RESULTS_DIR}/restoration.jsonl 2>/dev/null || true)
1465
+ kept_count=$(grep -c '"status":"kept"' "${KASEKI_RESULTS_DIR}"/restoration.jsonl 2>/dev/null || true)
1463
1466
  kept_count=${kept_count:-0}
1464
1467
  printf '[debug] restoration report: kept_count="%s"\n' "$kept_count" >&2
1465
1468
  if ! validate_numeric "kept_count" "$kept_count"; then
@@ -1497,12 +1500,12 @@ generate_restoration_report() {
1497
1500
  if [ "$restored_count" -gt 0 ]; then
1498
1501
  printf '## Restored Files\n\n'
1499
1502
  printf 'These files were modified by the agent but restored because they fall outside the allowlist:\n\n'
1500
- grep '"status":"restored"' ${KASEKI_RESULTS_DIR}/restoration.jsonl | \
1503
+ grep '"status":"restored"' "${KASEKI_RESULTS_DIR}"/restoration.jsonl | \
1501
1504
  sed "s/.*\"file\":\"\([^\"]*\)\".*/- \`\1\`/" | \
1502
- sort | uniq >> ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp 2>/dev/null || true
1503
- if [ -f ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp ]; then
1504
- cat ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp
1505
- rm -f ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp
1505
+ sort | uniq >> "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp 2>/dev/null || true
1506
+ if [ -f "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp ]; then
1507
+ cat "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp
1508
+ rm -f "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp
1506
1509
  fi
1507
1510
  printf '\n'
1508
1511
  fi
@@ -1510,12 +1513,12 @@ generate_restoration_report() {
1510
1513
  if [ "$kept_count" -gt 0 ]; then
1511
1514
  printf '## Kept Files (Allowlist Matches)\n\n'
1512
1515
  printf 'These files were in the allowlist and were kept:\n\n'
1513
- grep '"status":"kept"' ${KASEKI_RESULTS_DIR}/restoration.jsonl | \
1516
+ grep '"status":"kept"' "${KASEKI_RESULTS_DIR}"/restoration.jsonl | \
1514
1517
  sed "s/.*\"file\":\"\([^\"]*\)\".*/- \`\1\`/" | \
1515
- sort | uniq >> ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp 2>/dev/null || true
1516
- if [ -f ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp ]; then
1517
- cat ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp
1518
- rm -f ${KASEKI_RESULTS_DIR}/restoration-report.md.tmp
1518
+ sort | uniq >> "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp 2>/dev/null || true
1519
+ if [ -f "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp ]; then
1520
+ cat "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp
1521
+ rm -f "${KASEKI_RESULTS_DIR}"/restoration-report.md.tmp
1519
1522
  fi
1520
1523
  printf '\n'
1521
1524
  fi
@@ -1533,14 +1536,14 @@ generate_restoration_report() {
1533
1536
  printf 'KASEKI_CHANGED_FILES_ALLOWLIST="<your-pattern>" ./run-kaseki.sh\n'
1534
1537
  printf '```\n\n'
1535
1538
  printf "For help on allowlist patterns, see \`docs/QUALITY_GATES.md\`.\n"
1536
- } > ${KASEKI_RESULTS_DIR}/restoration-report.md
1539
+ } > "${KASEKI_RESULTS_DIR}"/restoration-report.md
1537
1540
  }
1538
1541
 
1539
1542
  check_validation_allowlist() {
1540
1543
  if [ -z "$KASEKI_VALIDATION_ALLOWLIST" ]; then
1541
1544
  return 0
1542
1545
  fi
1543
- if [ ! -d ${KASEKI_WORKSPACE_DIR}/repo/.git ]; then
1546
+ if [ ! -d "${KASEKI_WORKSPACE_DIR}"/repo/.git ]; then
1544
1547
  return 0
1545
1548
  fi
1546
1549
 
@@ -1566,14 +1569,14 @@ check_validation_allowlist() {
1566
1569
  }
1567
1570
  }
1568
1571
  ' "$validation_before_state_file" "$validation_after_state_file" | LC_ALL=C sort -u > "$validation_changed_file"
1569
- elif [ -f ${KASEKI_RESULTS_DIR}/changed-files.txt ]; then
1570
- cp ${KASEKI_RESULTS_DIR}/changed-files.txt "$validation_changed_file"
1572
+ elif [ -f "${KASEKI_RESULTS_DIR}"/changed-files.txt ]; then
1573
+ cp "${KASEKI_RESULTS_DIR}"/changed-files.txt "$validation_changed_file"
1571
1574
  fi
1572
1575
 
1573
1576
  while IFS= read -r changed_file || [ -n "$changed_file" ]; do
1574
1577
  [ -z "$changed_file" ] && continue
1575
1578
  if ! printf '%s\n' "$changed_file" | grep -Eq "^(${allowlist_regex})$"; then
1576
- printf 'Validation-phase file outside allowlist: %s\n' "$changed_file" | tee -a ${KASEKI_RESULTS_DIR}/quality.log
1579
+ printf 'Validation-phase file outside allowlist: %s\n' "$changed_file" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
1577
1580
  validation_violation_count=$((validation_violation_count + 1))
1578
1581
  emit_event "quality_gate_rule_evaluated" "rule=validation_allowlist" "passed=false" "file=$changed_file"
1579
1582
  else
@@ -1585,7 +1588,7 @@ check_validation_allowlist() {
1585
1588
  QUALITY_EXIT=7
1586
1589
  VALIDATION_ALLOWLIST_FAILURE_REASON="validation_allowlist_check: $validation_violation_count file(s) changed during validation outside KASEKI_VALIDATION_ALLOWLIST"
1587
1590
  QUALITY_FAILURE_REASON="$VALIDATION_ALLOWLIST_FAILURE_REASON"
1588
- printf '\n[validation-allowlist] %d file(s) modified during validation outside allowlist\n' "$validation_violation_count" | tee -a ${KASEKI_RESULTS_DIR}/quality.log
1591
+ printf '\n[validation-allowlist] %d file(s) modified during validation outside allowlist\n' "$validation_violation_count" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
1589
1592
  return 1
1590
1593
  fi
1591
1594
  return 0
@@ -1605,7 +1608,7 @@ check_secret_scan_allowlist() {
1605
1608
 
1606
1609
  # Read the log into a temp variable to avoid SC2094 (read-write in same pipeline)
1607
1610
  local temp_log
1608
- temp_log=$(cat ${KASEKI_RESULTS_DIR}/secret-scan.log)
1611
+ temp_log=$(cat "${KASEKI_RESULTS_DIR}"/secret-scan.log)
1609
1612
 
1610
1613
  while IFS= read -r match_line || [ -n "$match_line" ]; do
1611
1614
  [ -z "$match_line" ] && continue
@@ -1619,8 +1622,8 @@ check_secret_scan_allowlist() {
1619
1622
 
1620
1623
  [ -z "$pattern" ] && continue
1621
1624
 
1622
- # Normalize file path: remove leading ${KASEKI_WORKSPACE_DIR}/repo/, repo/, and ./ if present
1623
- file_path="${file_path#${KASEKI_WORKSPACE_DIR}/repo/}"
1625
+ # Normalize file path: remove leading "${KASEKI_WORKSPACE_DIR}"/repo/, repo/, and ./ if present
1626
+ file_path="${file_path#"${KASEKI_WORKSPACE_DIR}"/repo/}"
1624
1627
  file_path="${file_path#repo/}"
1625
1628
  file_path="${file_path#./}"
1626
1629
 
@@ -1645,7 +1648,7 @@ check_secret_scan_allowlist() {
1645
1648
  for match in "${secret_matches[@]}"; do
1646
1649
  printf '%s\n' "$match"
1647
1650
  done
1648
- } > ${KASEKI_RESULTS_DIR}/secret-scan.log
1651
+ } > "${KASEKI_RESULTS_DIR}"/secret-scan.log
1649
1652
 
1650
1653
  # Exit code 6 only if there are unallowlisted matches
1651
1654
  if [ "$unallowlisted_count" -gt 0 ]; then
@@ -1679,9 +1682,9 @@ finish() {
1679
1682
  printf '[unexpected-failure] Exit code: %d\n' "$code"
1680
1683
  printf '[unexpected-failure] Last command: %s\n' "$LAST_COMMAND"
1681
1684
  printf '[unexpected-failure] Current stage: %s\n' "$CURRENT_STAGE"
1682
- if [ -f ${KASEKI_RESULTS_DIR}/progress.log ]; then
1685
+ if [ -f "${KASEKI_RESULTS_DIR}"/progress.log ]; then
1683
1686
  printf '[unexpected-failure] Last 5 progress entries:\n'
1684
- tail -5 ${KASEKI_RESULTS_DIR}/progress.log | sed 's/^/ /'
1687
+ tail -5 "${KASEKI_RESULTS_DIR}"/progress.log | sed 's/^/ /'
1685
1688
  fi
1686
1689
  } | tee -a "$LAST_COMMAND_LOG" >&2
1687
1690
  emit_error_event "unexpected_shell_failure" "Uncaught shell error (exit $code) in stage '$CURRENT_STAGE'. Last command: $LAST_COMMAND. See $LAST_COMMAND_LOG for context." "exit"
@@ -1690,16 +1693,16 @@ finish() {
1690
1693
  maybe_call_finish_helper collect_git_artifacts
1691
1694
 
1692
1695
  # Analyze test failures and compare baseline vs. working results
1693
- if [ "$KASEKI_BASELINE_VALIDATION_ENABLED" = "1" ] && [ -f ${KASEKI_RESULTS_DIR}/validation-baseline.log ] && [ -f ${KASEKI_RESULTS_DIR}/pre-validation.log ]; then
1696
+ if [ "$KASEKI_BASELINE_VALIDATION_ENABLED" = "1" ] && [ -f "${KASEKI_RESULTS_DIR}"/validation-baseline.log ] && [ -f ${KASEKI_RESULTS_DIR}/pre-validation.log ]; then
1694
1697
  set_current_stage "test failure analysis"
1695
1698
  if analyze_test_failures_baseline; then
1696
1699
  TEST_FAILURE_CLASSIFICATION_STATUS="completed"
1697
1700
  # Try to extract newly_introduced_failures_count from JSON output (if jq available)
1698
- if [ -f ${KASEKI_RESULTS_DIR}/test-baseline-comparison.json ] && command -v jq >/dev/null 2>&1; then
1699
- NEWLY_INTRODUCED_FAILURES_COUNT=$(jq -r '.summary.total_newly_introduced // 0' ${KASEKI_RESULTS_DIR}/test-baseline-comparison.json 2>/dev/null || printf '0')
1700
- elif [ -f ${KASEKI_RESULTS_DIR}/test-baseline-comparison.json ]; then
1701
+ if [ -f "${KASEKI_RESULTS_DIR}"/test-baseline-comparison.json ] && command -v jq >/dev/null 2>&1; then
1702
+ NEWLY_INTRODUCED_FAILURES_COUNT=$(jq -r '.summary.total_newly_introduced // 0' "${KASEKI_RESULTS_DIR}"/test-baseline-comparison.json 2>/dev/null || printf '0')
1703
+ elif [ -f "${KASEKI_RESULTS_DIR}"/test-baseline-comparison.json ]; then
1701
1704
  # Fallback: try to extract with grep/sed if jq not available
1702
- NEWLY_INTRODUCED_FAILURES_COUNT=$(grep -o '"total_newly_introduced": [0-9]*' ${KASEKI_RESULTS_DIR}/test-baseline-comparison.json 2>/dev/null | grep -o '[0-9]*$' || printf '0')
1705
+ NEWLY_INTRODUCED_FAILURES_COUNT=$(grep -o '"total_newly_introduced": [0-9]*' "${KASEKI_RESULTS_DIR}"/test-baseline-comparison.json 2>/dev/null | grep -o '[0-9]*$' || printf '0')
1703
1706
  fi
1704
1707
  else
1705
1708
  TEST_FAILURE_CLASSIFICATION_STATUS="failed"
@@ -1713,8 +1716,8 @@ finish() {
1713
1716
  fi
1714
1717
 
1715
1718
  # Debug output for restoration report generation
1716
- if [ -f ${KASEKI_RESULTS_DIR}/restoration.jsonl ]; then
1717
- printf '[debug] restoration.jsonl exists (size=%d bytes)\n' "$(wc -c < ${KASEKI_RESULTS_DIR}/restoration.jsonl)" >&2
1719
+ if [ -f "${KASEKI_RESULTS_DIR}"/restoration.jsonl ]; then
1720
+ printf '[debug] restoration.jsonl exists (size=%d bytes)\n' "$(wc -c < "${KASEKI_RESULTS_DIR}"/restoration.jsonl)" >&2
1718
1721
  else
1719
1722
  printf '[debug] restoration.jsonl does not exist\n' >&2
1720
1723
  fi
@@ -1732,12 +1735,12 @@ finish() {
1732
1735
 
1733
1736
  # Calculate and record maturity score
1734
1737
  if [ -x /app/scripts/kaseki-maturity-score.sh ]; then
1735
- /app/scripts/kaseki-maturity-score.sh ${KASEKI_WORKSPACE_DIR}/repo ${KASEKI_RESULTS_DIR}/maturity-score.json 2>/dev/null || true
1738
+ /app/scripts/kaseki-maturity-score.sh "${KASEKI_WORKSPACE_DIR}"/repo "${KASEKI_RESULTS_DIR}"/maturity-score.json 2>/dev/null || true
1736
1739
  fi
1737
1740
 
1738
1741
  # Calculate and record performance metrics
1739
- if [ -x /app/scripts/kaseki-performance-metrics.sh ] && [ -f ${KASEKI_RESULTS_DIR}/stage-timings.tsv ]; then
1740
- /app/scripts/kaseki-performance-metrics.sh ${KASEKI_RESULTS_DIR}/stage-timings.tsv ${KASEKI_RESULTS_DIR}/performance-metrics.json 2>/dev/null || true
1742
+ if [ -x /app/scripts/kaseki-performance-metrics.sh ] && [ -f "${KASEKI_RESULTS_DIR}"/stage-timings.tsv ]; then
1743
+ /app/scripts/kaseki-performance-metrics.sh "${KASEKI_RESULTS_DIR}"/stage-timings.tsv ${KASEKI_RESULTS_DIR}/performance-metrics.json 2>/dev/null || true
1741
1744
  fi
1742
1745
 
1743
1746
  maybe_call_finish_helper write_result_summary
@@ -1790,7 +1793,7 @@ run_step_dry() {
1790
1793
  printf '\n==> %s (DRY-RUN: simulated)\n' "$label"
1791
1794
  emit_progress "$label" "started (dry-run)"
1792
1795
  # Show what commands would be run without executing them
1793
- printf '%s\n' "$@" >> ${KASEKI_RESULTS_DIR}/validation.log
1796
+ printf '%s\n' "$@" >> "${KASEKI_RESULTS_DIR}"/validation.log
1794
1797
  step_end="$(date +%s)"
1795
1798
  emit_progress "$label" "finished (dry-run, simulated exit 0)"
1796
1799
  record_stage_timing "$label" "0" "$((step_end - step_start))" "dry-run"
@@ -1825,9 +1828,9 @@ is_valid_git_mirror() {
1825
1828
  }
1826
1829
 
1827
1830
  run_direct_clone() {
1828
- rm -rf ${KASEKI_WORKSPACE_DIR}/repo
1831
+ rm -rf "${KASEKI_WORKSPACE_DIR}"/repo
1829
1832
  GIT_CLONE_STRATEGY="direct_shallow"
1830
- git clone --depth 1 --branch "$GIT_REF" "$REPO_URL" ${KASEKI_WORKSPACE_DIR}/repo
1833
+ git clone --depth 1 --branch "$GIT_REF" "$REPO_URL" "${KASEKI_WORKSPACE_DIR}"/repo
1831
1834
  }
1832
1835
 
1833
1836
  clone_with_git_cache() {
@@ -1904,25 +1907,25 @@ clone_with_git_cache() {
1904
1907
  fi
1905
1908
  flock -u 9 || true
1906
1909
 
1907
- rm -rf ${KASEKI_WORKSPACE_DIR}/repo
1910
+ rm -rf "${KASEKI_WORKSPACE_DIR}"/repo
1908
1911
  GIT_CLONE_STRATEGY="reference_shallow"
1909
- git clone --reference-if-able "$mirror" --depth 1 --branch "$GIT_REF" "$REPO_URL" ${KASEKI_WORKSPACE_DIR}/repo
1912
+ git clone --reference-if-able "$mirror" --depth 1 --branch "$GIT_REF" "$REPO_URL" "${KASEKI_WORKSPACE_DIR}"/repo
1910
1913
  clone_rc=$?
1911
1914
  if [ "$clone_rc" -eq 0 ]; then
1912
1915
  return 0
1913
1916
  fi
1914
1917
 
1915
- rm -rf ${KASEKI_WORKSPACE_DIR}/repo
1918
+ rm -rf "${KASEKI_WORKSPACE_DIR}"/repo
1916
1919
  GIT_CLONE_STRATEGY="mirror_local"
1917
1920
  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"
1918
- git clone --branch "$GIT_REF" "$mirror" ${KASEKI_WORKSPACE_DIR}/repo
1921
+ git clone --branch "$GIT_REF" "$mirror" "${KASEKI_WORKSPACE_DIR}"/repo
1919
1922
  clone_rc=$?
1920
- if [ "$clone_rc" -eq 0 ] && git -C ${KASEKI_WORKSPACE_DIR}/repo rev-parse --verify HEAD >/dev/null 2>&1; then
1921
- git -C ${KASEKI_WORKSPACE_DIR}/repo remote set-url origin "$REPO_URL" >/dev/null 2>&1 || true
1923
+ if [ "$clone_rc" -eq 0 ] && git -C "${KASEKI_WORKSPACE_DIR}"/repo rev-parse --verify HEAD >/dev/null 2>&1; then
1924
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo remote set-url origin "$REPO_URL" >/dev/null 2>&1 || true
1922
1925
  return 0
1923
1926
  fi
1924
1927
 
1925
- rm -rf ${KASEKI_WORKSPACE_DIR}/repo
1928
+ rm -rf "${KASEKI_WORKSPACE_DIR}"/repo
1926
1929
  GIT_CACHE_STATUS="mirror_clone_failed"
1927
1930
  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"
1928
1931
  run_direct_clone
@@ -2302,9 +2305,9 @@ run_typescript_precheck() {
2302
2305
  if ! has_npm_build_command "$KASEKI_TS_CHECK_COMMAND"; then
2303
2306
  local missing_script
2304
2307
  missing_script="$(npm_run_script_name "$KASEKI_TS_CHECK_COMMAND")" || missing_script="$KASEKI_TS_CHECK_COMMAND"
2305
- printf '\n==> TypeScript pre-check\n' | tee -a ${KASEKI_RESULTS_DIR}/pre-validation-ts-check.log
2306
- printf 'Command: %s\n' "$KASEKI_TS_CHECK_COMMAND" | tee -a ${KASEKI_RESULTS_DIR}/pre-validation-ts-check.log
2307
- printf 'skipped: npm script "%s" not found in package.json\n' "$missing_script" | tee -a ${KASEKI_RESULTS_DIR}/pre-validation-ts-check.log
2308
+ printf '\n==> TypeScript pre-check\n' | tee -a "${KASEKI_RESULTS_DIR}"/pre-validation-ts-check.log
2309
+ printf 'Command: %s\n' "$KASEKI_TS_CHECK_COMMAND" | tee -a "${KASEKI_RESULTS_DIR}"/pre-validation-ts-check.log
2310
+ printf 'skipped: npm script "%s" not found in package.json\n' "$missing_script" | tee -a "${KASEKI_RESULTS_DIR}"/pre-validation-ts-check.log
2308
2311
  emit_error_event "typescript_precheck_skipped_missing_script" "TypeScript check skipped: npm script '$missing_script' not defined" "continue"
2309
2312
  emit_progress "typescript precheck" "skipped (npm script '$missing_script' not found)"
2310
2313
  record_stage_timing "typescript precheck" 0 0 "skipped_missing_script"
@@ -2319,8 +2322,8 @@ run_typescript_precheck() {
2319
2322
  {
2320
2323
  printf '\n==> TypeScript pre-check\n'
2321
2324
  printf 'Command: %s\n' "$KASEKI_TS_CHECK_COMMAND"
2322
- eval "cd ${KASEKI_WORKSPACE_DIR}/repo && $KASEKI_TS_CHECK_COMMAND" 2>&1
2323
- } 2>&1 | tee -a ${KASEKI_RESULTS_DIR}/pre-validation-ts-check.log
2325
+ eval "cd "${KASEKI_WORKSPACE_DIR}"/repo && $KASEKI_TS_CHECK_COMMAND" 2>&1
2326
+ } 2>&1 | tee -a "${KASEKI_RESULTS_DIR}"/pre-validation-ts-check.log
2324
2327
  ts_check_exit="${PIPESTATUS[0]}"
2325
2328
 
2326
2329
  ts_check_end="$(date +%s)"
@@ -2393,14 +2396,14 @@ run_trailing_whitespace_cleanup_for_changed_tracked_text_files() {
2393
2396
  collect_changed_file_set() {
2394
2397
  local output_file="$1"
2395
2398
  : > "$output_file"
2396
- if [ ! -d ${KASEKI_WORKSPACE_DIR}/repo/.git ]; then
2399
+ if [ ! -d "${KASEKI_WORKSPACE_DIR}"/repo/.git ]; then
2397
2400
  return 0
2398
2401
  fi
2399
2402
 
2400
2403
  {
2401
- git -C ${KASEKI_WORKSPACE_DIR}/repo diff --name-only -- . 2>/dev/null || true
2402
- git -C ${KASEKI_WORKSPACE_DIR}/repo diff --name-only --cached -- . 2>/dev/null || true
2403
- git -C ${KASEKI_WORKSPACE_DIR}/repo ls-files --others --exclude-standard 2>/dev/null || true
2404
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo diff --name-only -- . 2>/dev/null || true
2405
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo diff --name-only --cached -- . 2>/dev/null || true
2406
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo ls-files --others --exclude-standard 2>/dev/null || true
2404
2407
  } | sed '/^$/d' | LC_ALL=C sort -u > "$output_file"
2405
2408
  }
2406
2409
 
@@ -2408,7 +2411,7 @@ collect_changed_file_state() {
2408
2411
  local output_file="$1"
2409
2412
  local changed_files_file path staged_hash unstaged_hash content_hash state
2410
2413
  : > "$output_file"
2411
- if [ ! -d ${KASEKI_WORKSPACE_DIR}/repo/.git ]; then
2414
+ if [ ! -d "${KASEKI_WORKSPACE_DIR}"/repo/.git ]; then
2412
2415
  return 0
2413
2416
  fi
2414
2417
 
@@ -2417,12 +2420,12 @@ collect_changed_file_state() {
2417
2420
 
2418
2421
  while IFS= read -r path || [ -n "$path" ]; do
2419
2422
  [ -z "$path" ] && continue
2420
- if git -C ${KASEKI_WORKSPACE_DIR}/repo ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
2421
- staged_hash="$(git -C ${KASEKI_WORKSPACE_DIR}/repo diff --binary --cached -- "$path" 2>/dev/null | sha256sum | awk '{print $1}')"
2422
- unstaged_hash="$(git -C ${KASEKI_WORKSPACE_DIR}/repo diff --binary -- "$path" 2>/dev/null | sha256sum | awk '{print $1}')"
2423
+ if git -C "${KASEKI_WORKSPACE_DIR}"/repo ls-files --error-unmatch -- "$path" >/dev/null 2>&1; then
2424
+ staged_hash="$(git -C "${KASEKI_WORKSPACE_DIR}"/repo diff --binary --cached -- "$path" 2>/dev/null | sha256sum | awk '{print $1}')"
2425
+ unstaged_hash="$(git -C "${KASEKI_WORKSPACE_DIR}"/repo diff --binary -- "$path" 2>/dev/null | sha256sum | awk '{print $1}')"
2423
2426
  state="tracked:staged=${staged_hash}:unstaged=${unstaged_hash}"
2424
2427
  elif [ -f "${KASEKI_WORKSPACE_DIR}/repo/$path" ]; then
2425
- content_hash="$(git -C ${KASEKI_WORKSPACE_DIR}/repo hash-object --no-filters -- "$path" 2>/dev/null || sha256sum "${KASEKI_WORKSPACE_DIR}/repo/$path" 2>/dev/null | awk '{print $1}')"
2428
+ content_hash="$(git -C "${KASEKI_WORKSPACE_DIR}"/repo hash-object --no-filters -- "$path" 2>/dev/null || sha256sum "${KASEKI_WORKSPACE_DIR}/repo/$path" 2>/dev/null | awk '{print $1}')"
2426
2429
  state="untracked:file=${content_hash}"
2427
2430
  elif [ -d "${KASEKI_WORKSPACE_DIR}/repo/$path" ]; then
2428
2431
  state="untracked:directory"
@@ -2442,10 +2445,10 @@ restore_cleanup_disallowed_changes() {
2442
2445
 
2443
2446
  while IFS= read -r changed_file || [ -n "$changed_file" ]; do
2444
2447
  [ -z "$changed_file" ] && continue
2445
- printf 'Restoring cleanup-created file outside allowlist: %s\n' "$changed_file" | tee -a "$AUTO_LINT_CLEANUP_LOG" ${KASEKI_RESULTS_DIR}/quality.log
2448
+ printf 'Restoring cleanup-created file outside allowlist: %s\n' "$changed_file" | tee -a "$AUTO_LINT_CLEANUP_LOG" "${KASEKI_RESULTS_DIR}"/quality.log
2446
2449
  emit_event "auto_lint_cleanup_file_restored" "file=$changed_file" "reason=not_in_cleanup_allowlist"
2447
- git -C ${KASEKI_WORKSPACE_DIR}/repo restore --staged --worktree -- "$changed_file" 2>/dev/null || true
2448
- git -C ${KASEKI_WORKSPACE_DIR}/repo clean -f -- "$changed_file" 2>/dev/null || true
2450
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo restore --staged --worktree -- "$changed_file" 2>/dev/null || true
2451
+ git -C "${KASEKI_WORKSPACE_DIR}"/repo clean -f -- "$changed_file" 2>/dev/null || true
2449
2452
  done < "$disallowed_file"
2450
2453
  }
2451
2454
 
@@ -2460,7 +2463,7 @@ check_auto_lint_cleanup_allowlist() {
2460
2463
  : > "$cleanup_created_file"
2461
2464
  : > "$disallowed_file"
2462
2465
  : > "$post_restore_file"
2463
- if [ ! -d ${KASEKI_WORKSPACE_DIR}/repo/.git ]; then
2466
+ if [ ! -d "${KASEKI_WORKSPACE_DIR}"/repo/.git ]; then
2464
2467
  return 0
2465
2468
  fi
2466
2469
 
@@ -2475,7 +2478,7 @@ check_auto_lint_cleanup_allowlist() {
2475
2478
  if [ -n "$allowlist_regex" ] && printf '%s\n' "$changed_file" | grep -Eq "^(${allowlist_regex})$"; then
2476
2479
  emit_event "quality_gate_rule_evaluated" "rule=auto_lint_cleanup_allowlist" "passed=true" "file=$changed_file"
2477
2480
  else
2478
- printf 'Auto lint cleanup created changed file outside allowlist: %s\n' "$changed_file" | tee -a "$AUTO_LINT_CLEANUP_LOG" ${KASEKI_RESULTS_DIR}/quality.log
2481
+ printf 'Auto lint cleanup created changed file outside allowlist: %s\n' "$changed_file" | tee -a "$AUTO_LINT_CLEANUP_LOG" "${KASEKI_RESULTS_DIR}"/quality.log
2479
2482
  printf '%s\n' "$changed_file" >> "$disallowed_file"
2480
2483
  emit_event "quality_gate_rule_evaluated" "rule=auto_lint_cleanup_allowlist" "passed=false" "file=$changed_file"
2481
2484
  fi
@@ -2492,12 +2495,12 @@ check_auto_lint_cleanup_allowlist() {
2492
2495
  while IFS= read -r changed_file || [ -n "$changed_file" ]; do
2493
2496
  [ -z "$changed_file" ] && continue
2494
2497
  if grep -Fxq -- "$changed_file" "$post_restore_file"; then
2495
- printf 'ERROR: Cleanup-created disallowed change could not be restored: %s\n' "$changed_file" | tee -a "$AUTO_LINT_CLEANUP_LOG" ${KASEKI_RESULTS_DIR}/quality.log
2498
+ printf 'ERROR: Cleanup-created disallowed change could not be restored: %s\n' "$changed_file" | tee -a "$AUTO_LINT_CLEANUP_LOG" "${KASEKI_RESULTS_DIR}"/quality.log
2496
2499
  unrestored_count=$((unrestored_count + 1))
2497
2500
  fi
2498
2501
  done < "$disallowed_file"
2499
2502
  if [ "$unrestored_count" -eq 0 ]; then
2500
- printf 'Auto lint cleanup restored %s cleanup-created file(s) outside allowlist.\n' "$disallowed_count" | tee -a "$AUTO_LINT_CLEANUP_LOG" ${KASEKI_RESULTS_DIR}/quality.log
2503
+ printf 'Auto lint cleanup restored %s cleanup-created file(s) outside allowlist.\n' "$disallowed_count" | tee -a "$AUTO_LINT_CLEANUP_LOG" "${KASEKI_RESULTS_DIR}"/quality.log
2501
2504
  emit_event "auto_lint_cleanup_allowlist_restoration_complete" "restored=$disallowed_count" "unrestored=0"
2502
2505
  collect_git_artifacts
2503
2506
  return 0
@@ -2510,7 +2513,7 @@ check_auto_lint_cleanup_allowlist() {
2510
2513
  AUTO_LINT_CLEANUP_FAILURE_CLASSIFICATION="cleanup_allowlist_failed"
2511
2514
  QUALITY_EXIT=7
2512
2515
  QUALITY_FAILURE_REASON="auto_lint_cleanup_allowlist: $disallowed_count cleanup-created file(s) outside KASEKI_CHANGED_FILES_ALLOWLIST/KASEKI_VALIDATION_ALLOWLIST"
2513
- printf 'ERROR: %s\n' "$QUALITY_FAILURE_REASON" | tee -a "$AUTO_LINT_CLEANUP_LOG" ${KASEKI_RESULTS_DIR}/quality.log
2516
+ printf 'ERROR: %s\n' "$QUALITY_FAILURE_REASON" | tee -a "$AUTO_LINT_CLEANUP_LOG" "${KASEKI_RESULTS_DIR}"/quality.log
2514
2517
  emit_error_event "auto_lint_cleanup_allowlist_failed" "$QUALITY_FAILURE_REASON" "continue"
2515
2518
  return 1
2516
2519
  }
@@ -2570,12 +2573,12 @@ run_auto_lint_cleanup() {
2570
2573
  return 0
2571
2574
  fi
2572
2575
 
2573
- if ! [ -d ${KASEKI_WORKSPACE_DIR}/repo ]; then
2576
+ if ! [ -d "${KASEKI_WORKSPACE_DIR}"/repo ]; then
2574
2577
  AUTO_LINT_CLEANUP_EXIT=1
2575
2578
  AUTO_LINT_CLEANUP_RESULT="failed"
2576
2579
  AUTO_LINT_CLEANUP_CLASSIFICATION="directory_missing"
2577
2580
  AUTO_LINT_CLEANUP_FAILURE_CLASSIFICATION="directory_missing"
2578
- printf 'ERROR: Working directory ${KASEKI_WORKSPACE_DIR}/repo does not exist before auto lint cleanup.\n' | tee -a "$AUTO_LINT_CLEANUP_LOG"
2581
+ printf 'ERROR: Working directory "${KASEKI_WORKSPACE_DIR}"/repo does not exist before auto lint cleanup.\n' | tee -a "$AUTO_LINT_CLEANUP_LOG"
2579
2582
  printf 'workspace_missing\t%s\t0\tclassification=directory_missing\n' "$AUTO_LINT_CLEANUP_EXIT" >> "$AUTO_LINT_CLEANUP_TIMINGS_FILE"
2580
2583
  record_stage_timing "$stage_label" "$AUTO_LINT_CLEANUP_EXIT" "$(($(date +%s) - stage_start))" "directory_missing classification=directory_missing"
2581
2584
  emit_event "auto_lint_cleanup_finished" "exit_code=$AUTO_LINT_CLEANUP_EXIT" "result=failed" "classification=directory_missing" "reason=directory_missing"
@@ -2710,17 +2713,17 @@ restore_baseline_validation_from_cache() {
2710
2713
  # Restore cached files to results directory
2711
2714
  mkdir -p ${KASEKI_RESULTS_DIR}
2712
2715
 
2713
- if ! cp "$cache_dir/validation.log" ${KASEKI_RESULTS_DIR}/validation-baseline.log 2>/dev/null; then
2716
+ if ! cp "$cache_dir/validation.log" "${KASEKI_RESULTS_DIR}"/validation-baseline.log 2>/dev/null; then
2714
2717
  return 1
2715
2718
  fi
2716
- if ! cp "$cache_dir/validation-raw.log" ${KASEKI_RESULTS_DIR}/validation-baseline-raw.log 2>/dev/null; then
2719
+ if ! cp "$cache_dir/validation-raw.log" "${KASEKI_RESULTS_DIR}"/validation-baseline-raw.log 2>/dev/null; then
2717
2720
  return 1
2718
2721
  fi
2719
- if ! cp "$cache_dir/validation-timings.tsv" ${KASEKI_RESULTS_DIR}/validation-baseline-timings.tsv 2>/dev/null; then
2722
+ if ! cp "$cache_dir/validation-timings.tsv" "${KASEKI_RESULTS_DIR}"/validation-baseline-timings.tsv 2>/dev/null; then
2720
2723
  return 1
2721
2724
  fi
2722
2725
  if [ -f "$cache_dir/validation-env.log" ]; then
2723
- cp "$cache_dir/validation-env.log" ${KASEKI_RESULTS_DIR}/validation-baseline-env.log 2>/dev/null || true
2726
+ cp "$cache_dir/validation-env.log" "${KASEKI_RESULTS_DIR}"/validation-baseline-env.log 2>/dev/null || true
2724
2727
  fi
2725
2728
 
2726
2729
  return 0
@@ -2737,20 +2740,20 @@ save_baseline_validation_to_cache() {
2737
2740
  mkdir -p "$cache_dir" || return 1
2738
2741
 
2739
2742
  # Save validation results to cache
2740
- if [ -f ${KASEKI_RESULTS_DIR}/validation-baseline.log ]; then
2741
- cp ${KASEKI_RESULTS_DIR}/validation-baseline.log "$cache_dir/validation.log" || return 1
2743
+ if [ -f "${KASEKI_RESULTS_DIR}"/validation-baseline.log ]; then
2744
+ cp "${KASEKI_RESULTS_DIR}"/validation-baseline.log "$cache_dir/validation.log" || return 1
2742
2745
  fi
2743
2746
 
2744
- if [ -f ${KASEKI_RESULTS_DIR}/validation-baseline-raw.log ]; then
2745
- cp ${KASEKI_RESULTS_DIR}/validation-baseline-raw.log "$cache_dir/validation-raw.log" || return 1
2747
+ if [ -f "${KASEKI_RESULTS_DIR}"/validation-baseline-raw.log ]; then
2748
+ cp "${KASEKI_RESULTS_DIR}"/validation-baseline-raw.log "$cache_dir/validation-raw.log" || return 1
2746
2749
  fi
2747
2750
 
2748
- if [ -f ${KASEKI_RESULTS_DIR}/validation-baseline-timings.tsv ]; then
2749
- cp ${KASEKI_RESULTS_DIR}/validation-baseline-timings.tsv "$cache_dir/validation-timings.tsv" || return 1
2751
+ if [ -f "${KASEKI_RESULTS_DIR}"/validation-baseline-timings.tsv ]; then
2752
+ cp "${KASEKI_RESULTS_DIR}"/validation-baseline-timings.tsv "$cache_dir/validation-timings.tsv" || return 1
2750
2753
  fi
2751
2754
 
2752
- if [ -f ${KASEKI_RESULTS_DIR}/validation-baseline-env.log ]; then
2753
- cp ${KASEKI_RESULTS_DIR}/validation-baseline-env.log "$cache_dir/validation-env.log" 2>/dev/null || true
2755
+ if [ -f "${KASEKI_RESULTS_DIR}"/validation-baseline-env.log ]; then
2756
+ cp "${KASEKI_RESULTS_DIR}"/validation-baseline-env.log "$cache_dir/validation-env.log" 2>/dev/null || true
2754
2757
  fi
2755
2758
 
2756
2759
  return 0
@@ -2776,10 +2779,10 @@ checkout_baseline_repo() {
2776
2779
  emit_progress "baseline preparation" "installing baseline dependencies"
2777
2780
  if ! cd "$baseline_dir" && npm ci --prefer-offline 2>>"$KASEKI_LOG_DIR/baseline-npm-ci.log"; then
2778
2781
  emit_error_event "baseline_deps_failed" "Failed to install baseline dependencies" "continue"
2779
- cd ${KASEKI_WORKSPACE_DIR}/repo
2782
+ cd "${KASEKI_WORKSPACE_DIR}"/repo
2780
2783
  return 1
2781
2784
  fi
2782
- cd ${KASEKI_WORKSPACE_DIR}/repo
2785
+ cd "${KASEKI_WORKSPACE_DIR}"/repo
2783
2786
  fi
2784
2787
 
2785
2788
  return 0
@@ -3025,13 +3028,13 @@ run_validation_commands() {
3025
3028
  record_stage_timing "$stage_label" 0 0 "skipped_by_config"
3026
3029
  else
3027
3030
  # Checkpoint: Verify working directory exists before validation.
3028
- if ! [ -d ${KASEKI_WORKSPACE_DIR}/repo ]; then
3029
- printf 'ERROR: Working directory ${KASEKI_WORKSPACE_DIR}/repo does not exist before %s\n' "$stage_label" | tee -a "$log_file"
3031
+ if ! [ -d "${KASEKI_WORKSPACE_DIR}"/repo ]; then
3032
+ printf 'ERROR: Working directory "${KASEKI_WORKSPACE_DIR}"/repo does not exist before %s\n' "$stage_label" | tee -a "$log_file"
3030
3033
  printf 'Current pwd: %s\n' "$(pwd 2>&1 || echo '<pwd failed>')" | tee -a "$log_file"
3031
3034
  printf 'Filesystem state:\n' | tee -a "$log_file"
3032
3035
  find /workspace -maxdepth 3 -type f 2>&1 | head -100 | tee -a "$log_file"
3033
3036
  validation_exit_ref=1
3034
- validation_detail_ref="Working directory ${KASEKI_WORKSPACE_DIR}/repo missing before $stage_label"
3037
+ validation_detail_ref="Working directory "${KASEKI_WORKSPACE_DIR}"/repo missing before $stage_label"
3035
3038
  validation_reason_ref="$failure_reason_prefix: workspace_missing"
3036
3039
  record_stage_timing "$stage_label" "$validation_exit_ref" "$(($(date +%s) - stage_start))" "directory_missing"
3037
3040
  else
@@ -3057,7 +3060,7 @@ run_validation_commands() {
3057
3060
  printf '[validation command] working_directory=%s\n' "$(pwd 2>&1 || echo '<pwd failed>')"
3058
3061
  printf '[validation command] node_version=%s\n' "$(node --version 2>&1 || echo '<node not found>')"
3059
3062
  printf '[validation command] npm_version=%s\n' "$(npm --version 2>&1 || echo '<npm not found>')"
3060
- printf '[validation command] disk_available=%s\n' "$(df -h ${KASEKI_RESULTS_DIR} 2>/dev/null | tail -1 | awk '{print $4}' || echo '<df failed>')"
3063
+ printf '[validation command] disk_available=%s\n' "$(df -h "${KASEKI_RESULTS_DIR}" 2>/dev/null | tail -1 | awk '{print $4}' || echo '<df failed>')"
3061
3064
  } | tee -a "$env_log"
3062
3065
  # Use pipefail to catch errors in any stage of the pipe.
3063
3066
  pipefail_was_enabled=0
@@ -3119,7 +3122,7 @@ run_validation_commands() {
3119
3122
  {
3120
3123
  printf '\n[DIAGNOSTICS] Validation pipeline stderr from filter/tee (last 50 lines):\n'
3121
3124
  printf '%s\n' "$FILTER_STDERR_TAIL"
3122
- } | tee -a "$log_file" ${KASEKI_RESULTS_DIR}/quality.log
3125
+ } | tee -a "$log_file" "${KASEKI_RESULTS_DIR}"/quality.log
3123
3126
  {
3124
3127
  printf '\n[validation pipeline stderr tail]\n'
3125
3128
  printf '%s\n' "$FILTER_STDERR_TAIL"
@@ -3147,7 +3150,7 @@ run_validation_commands() {
3147
3150
  else
3148
3151
  printf ' (No stderr captured from filter/tee)\n'
3149
3152
  fi
3150
- } | tee -a "$log_file" ${KASEKI_RESULTS_DIR}/quality.log "$FILTER_DIAGNOSTICS_LOG"
3153
+ } | tee -a "$log_file" "${KASEKI_RESULTS_DIR}"/quality.log "$FILTER_DIAGNOSTICS_LOG"
3151
3154
  fi
3152
3155
 
3153
3156
  if [ "$validation_infra_failure" = "true" ] && [ "$validation_exit_ref" -eq 0 ]; then
@@ -3171,13 +3174,13 @@ run_validation_commands() {
3171
3174
  printf '\n[DIAGNOSTICS] Validation command failed with directory access error:\n'
3172
3175
  printf 'Working directory status:\n'
3173
3176
  printf ' Current pwd: %s\n' "$(pwd 2>&1 || echo '<pwd failed>')"
3174
- printf ' ${KASEKI_WORKSPACE_DIR}/repo exists: %s\n' "$([ -d ${KASEKI_WORKSPACE_DIR}/repo ] && echo 'yes' || echo 'no')"
3175
- if [ -L ${KASEKI_WORKSPACE_DIR}/repo/node_modules ]; then
3176
- printf ' node_modules is symlink → %s\n' "$(readlink ${KASEKI_WORKSPACE_DIR}/repo/node_modules 2>&1 || echo '<readlink failed>')"
3177
+ printf ' "${KASEKI_WORKSPACE_DIR}"/repo exists: %s\n' "$([ -d ${KASEKI_WORKSPACE_DIR}/repo ] && echo 'yes' || echo 'no')"
3178
+ if [ -L "${KASEKI_WORKSPACE_DIR}"/repo/node_modules ]; then
3179
+ printf ' node_modules is symlink → %s\n' "$(readlink "${KASEKI_WORKSPACE_DIR}"/repo/node_modules 2>&1 || echo '<readlink failed>')"
3177
3180
  fi
3178
3181
  printf 'Last 20 lines of validation log:\n'
3179
3182
  tail -20 "$log_file"
3180
- } | tee -a ${KASEKI_RESULTS_DIR}/quality.log
3183
+ } | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
3181
3184
  fi
3182
3185
  # Fail-fast: if enabled, stop validation loop at first failure.
3183
3186
  if [ "$KASEKI_VALIDATION_FAIL_FAST" -eq 1 ]; then
@@ -3258,7 +3261,7 @@ write_repo_memory_summary() {
3258
3261
  return 0
3259
3262
  fi
3260
3263
  local updated_at
3261
- REPO_MEMORY_COMMIT_SHA="$(git -C ${KASEKI_WORKSPACE_DIR}/repo rev-parse HEAD 2>/dev/null || printf 'unknown')"
3264
+ REPO_MEMORY_COMMIT_SHA="$(git -C "${KASEKI_WORKSPACE_DIR}"/repo rev-parse HEAD 2>/dev/null || printf 'unknown')"
3262
3265
  updated_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
3263
3266
  node - "$KASEKI_REPO_MEMORY_MAX_BYTES" "$REPO_MEMORY_FILE" "$KASEKI_RESULTS_DIR" "$REPO_URL" "$GIT_REF" "$REPO_MEMORY_COMMIT_SHA" "$updated_at" "$KASEKI_TASK_MODE" "$STATUS" "$PI_EXIT" "$VALIDATION_EXIT" "$QUALITY_EXIT" "$SECRET_SCAN_EXIT" <<'NODE' || {
3264
3267
  const fs = require('fs');
@@ -3438,9 +3441,9 @@ is_transient_goal_setting_failure() {
3438
3441
  local stderr_content="$2"
3439
3442
 
3440
3443
  # First, check if we have an explicit validation reason code from our helper
3441
- if [ -f ${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt ]; then
3444
+ if [ -f "${KASEKI_RESULTS_DIR}"/goal-setting-validation-reason.txt ]; then
3442
3445
  local reason_code
3443
- reason_code=$(cat ${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt 2>/dev/null || echo "")
3446
+ reason_code=$(cat "${KASEKI_RESULTS_DIR}"/goal-setting-validation-reason.txt 2>/dev/null || echo "")
3444
3447
  case "$reason_code" in
3445
3448
  valid)
3446
3449
  return 1
@@ -3619,7 +3622,7 @@ validate_goal_setting_artifact() {
3619
3622
  local candidate_artifact="$1"
3620
3623
  local final_artifact="$2"
3621
3624
  local reason_file="$3"
3622
- local results_dir="${KASEKI_RESULTS_DIR:-${KASEKI_RESULTS_DIR}}"
3625
+ local results_dir="$KASEKI_RESULTS_DIR"
3623
3626
 
3624
3627
  if ! [ -f "$candidate_artifact" ]; then
3625
3628
  {
@@ -3659,7 +3662,7 @@ validate_goal_setting_artifact() {
3659
3662
  validate_goal_setting_artifact_with_node() {
3660
3663
  local candidate_artifact="$1"
3661
3664
  local reason_file="$2"
3662
- local results_dir="${KASEKI_RESULTS_DIR:-${KASEKI_RESULTS_DIR}}"
3665
+ local results_dir="$KASEKI_RESULTS_DIR"
3663
3666
 
3664
3667
  local validation_output
3665
3668
  validation_output=$(node -e "
@@ -3844,13 +3847,13 @@ run_goal_setting_agent() {
3844
3847
  set_current_stage "pi goal-setting agent"
3845
3848
 
3846
3849
  if [ "$KASEKI_GOAL_SETTING" = "0" ]; then
3847
- printf 'Pi goal-setting agent skipped because KASEKI_GOAL_SETTING=0.\n' | tee -a ${KASEKI_RESULTS_DIR}/goal-setting-stderr.log
3850
+ printf 'Pi goal-setting agent skipped because KASEKI_GOAL_SETTING=0.\n' | tee -a "${KASEKI_RESULTS_DIR}"/goal-setting-stderr.log
3848
3851
  record_stage_timing "pi goal-setting agent" 0 0 "skipped_by_config"
3849
3852
  return 0
3850
3853
  fi
3851
3854
 
3852
3855
  if [ "$KASEKI_DRY_RUN" = "1" ]; then
3853
- printf 'DRY-RUN: Pi goal-setting agent would upgrade the task prompt into a mature goal.\n' | tee -a ${KASEKI_RESULTS_DIR}/goal-setting-stderr.log
3856
+ printf 'DRY-RUN: Pi goal-setting agent would upgrade the task prompt into a mature goal.\n' | tee -a "${KASEKI_RESULTS_DIR}"/goal-setting-stderr.log
3854
3857
  record_stage_timing "pi goal-setting agent" 0 0 "dry_run=true"
3855
3858
  return 0
3856
3859
  fi
@@ -3862,9 +3865,9 @@ run_goal_setting_agent() {
3862
3865
  OPENROUTER_API_KEY="$openrouter_api_key" \
3863
3866
  timeout --signal=SIGTERM "$KASEKI_GOAL_SETTING_TIMEOUT_SECONDS" \
3864
3867
  pi --mode json --no-session --provider "$KASEKI_PROVIDER" --model "$KASEKI_GOAL_SETTING_MODEL" "$goal_setting_prompt" \
3865
- 2> >(tee -a ${KASEKI_RESULTS_DIR}/goal-setting-stderr.log >&2) \
3868
+ 2> >(tee -a "${KASEKI_RESULTS_DIR}"/goal-setting-stderr.log >&2) \
3866
3869
  | tee "$GOAL_SETTING_RAW_EVENTS" \
3867
- | kaseki-pi-progress-stream ${KASEKI_RESULTS_DIR}/progress.jsonl ${KASEKI_RESULTS_DIR}/progress.log
3870
+ | kaseki-pi-progress-stream "${KASEKI_RESULTS_DIR}"/progress.jsonl "${KASEKI_RESULTS_DIR}"/progress.log
3868
3871
  GOAL_SETTING_EXIT="${PIPESTATUS[0]}"
3869
3872
  GOAL_SETTING_DURATION_SECONDS=$(($(date +%s) - goal_setting_start))
3870
3873
  unset goal_setting_prompt
@@ -3872,12 +3875,12 @@ run_goal_setting_agent() {
3872
3875
 
3873
3876
  if [ "$GOAL_SETTING_EXIT" -eq 0 ] && ! validate_goal_setting_artifact "$GOAL_SETTING_CANDIDATE_ARTIFACT" "$GOAL_SETTING_ARTIFACT" "${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt"; then
3874
3877
  GOAL_SETTING_EXIT=86
3875
- goal_setting_validation_summary="$(cat ${KASEKI_RESULTS_DIR}/goal-setting-validation-summary.txt 2>/dev/null || printf 'goal-setting artifact validation failed')"
3876
- emit_error_event "pi_goal_setting_artifact_invalid" "Pi goal-setting artifact invalid: $goal_setting_validation_summary (full details: ${KASEKI_RESULTS_DIR}/goal-setting-validation-errors.jsonl)" "continue"
3878
+ goal_setting_validation_summary="$(cat "${KASEKI_RESULTS_DIR}"/goal-setting-validation-summary.txt 2>/dev/null || printf 'goal-setting artifact validation failed')"
3879
+ emit_error_event "pi_goal_setting_artifact_invalid" "Pi goal-setting artifact invalid: $goal_setting_validation_summary (full details: "${KASEKI_RESULTS_DIR}"/goal-setting-validation-errors.jsonl)" "continue"
3877
3880
  fi
3878
3881
 
3879
3882
  rm -f "$GOAL_SETTING_CANDIDATE_ARTIFACT"
3880
- kaseki-pi-event-filter "$GOAL_SETTING_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/goal-setting-events.jsonl ${KASEKI_RESULTS_DIR}/goal-setting-summary.json 2>> ${KASEKI_RESULTS_DIR}/goal-setting-stderr.log || cp "$GOAL_SETTING_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/goal-setting-events.raw.jsonl 2>/dev/null || true
3883
+ kaseki-pi-event-filter "$GOAL_SETTING_RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/goal-setting-events.jsonl ${KASEKI_RESULTS_DIR}/goal-setting-summary.json 2>> "${KASEKI_RESULTS_DIR}"/goal-setting-stderr.log || cp "$GOAL_SETTING_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/goal-setting-events.raw.jsonl 2>/dev/null || true
3881
3884
  GOAL_SETTING_ACTUAL_MODEL="$(node -e 'try { const s=require(process.env.KASEKI_RESULTS_DIR + "/goal-setting-summary.json"); const v=String(s.selected_model || s.model || "").trim(); console.log(v && v !== "unknown" && v !== "null" ? v : "unknown"); } catch { console.log("unknown"); }' 2>/dev/null)"
3882
3885
 
3883
3886
  record_stage_timing "pi goal-setting agent" "$GOAL_SETTING_EXIT" "$GOAL_SETTING_DURATION_SECONDS" "artifact=$GOAL_SETTING_ARTIFACT timeout_seconds=$KASEKI_GOAL_SETTING_TIMEOUT_SECONDS"
@@ -3888,7 +3891,7 @@ run_goal_setting_agent() {
3888
3891
  fi
3889
3892
 
3890
3893
  emit_progress "pi goal-setting agent" "wrote goal-setting artifact"
3891
- rm -f ${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt 2>/dev/null || true
3894
+ rm -f "${KASEKI_RESULTS_DIR}"/goal-setting-validation-reason.txt 2>/dev/null || true
3892
3895
 
3893
3896
  return 0
3894
3897
  }
@@ -3939,7 +3942,7 @@ write_goal_setting_metrics() {
3939
3942
  printf ' "model": "%s",\n' "${GOAL_SETTING_ACTUAL_MODEL:-unknown}"
3940
3943
  printf ' "timeout_seconds": %d\n' "${KASEKI_GOAL_SETTING_TIMEOUT_SECONDS:-300}"
3941
3944
  printf '}\n'
3942
- } > ${KASEKI_RESULTS_DIR}/goal-setting-metrics.json
3945
+ } > "${KASEKI_RESULTS_DIR}"/goal-setting-metrics.json
3943
3946
  }
3944
3947
  }
3945
3948
 
@@ -3948,9 +3951,9 @@ classify_goal_setting_error() {
3948
3951
  local stderr_content="$2"
3949
3952
 
3950
3953
  # Check validation reason file first (most authoritative)
3951
- if [ -f ${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt ]; then
3954
+ if [ -f "${KASEKI_RESULTS_DIR}"/goal-setting-validation-reason.txt ]; then
3952
3955
  local reason_code
3953
- reason_code=$(cat ${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt 2>/dev/null || echo "")
3956
+ reason_code=$(cat "${KASEKI_RESULTS_DIR}"/goal-setting-validation-reason.txt 2>/dev/null || echo "")
3954
3957
  case "$reason_code" in
3955
3958
  schema_mismatch)
3956
3959
  echo "GOAL_SETTING_SCHEMA_MISMATCH"
@@ -4029,7 +4032,7 @@ run_goal_setting_agent_with_retry() {
4029
4032
  set -e
4030
4033
 
4031
4034
  # Append stderr to results for logging
4032
- cat "$goal_setting_stderr_capture" >> ${KASEKI_RESULTS_DIR}/goal-setting-stderr.log 2>/dev/null || true
4035
+ cat "$goal_setting_stderr_capture" >> "${KASEKI_RESULTS_DIR}"/goal-setting-stderr.log 2>/dev/null || true
4033
4036
  goal_setting_last_stderr="$(cat "$goal_setting_stderr_capture" 2>/dev/null || true)"
4034
4037
  rm -f "$goal_setting_stderr_capture"
4035
4038
 
@@ -4061,7 +4064,7 @@ run_goal_setting_agent_with_retry() {
4061
4064
  attempt=$((attempt + 1))
4062
4065
  # Reset goal-setting artifacts for retry
4063
4066
  rm -f "$GOAL_SETTING_ARTIFACT" "$GOAL_SETTING_RAW_EVENTS" 2>/dev/null || true
4064
- rm -f ${KASEKI_RESULTS_DIR}/goal-setting-validation-reason.txt 2>/dev/null || true
4067
+ rm -f "${KASEKI_RESULTS_DIR}"/goal-setting-validation-reason.txt 2>/dev/null || true
4065
4068
  continue
4066
4069
  fi
4067
4070
  else
@@ -4095,9 +4098,9 @@ is_transient_scouting_failure() {
4095
4098
  local stderr_content="$2"
4096
4099
 
4097
4100
  # First, check if we have an explicit validation reason code from our helper
4098
- if [ -f ${KASEKI_RESULTS_DIR}/scouting-validation-reason.txt ]; then
4101
+ if [ -f "${KASEKI_RESULTS_DIR}"/scouting-validation-reason.txt ]; then
4099
4102
  local reason_code
4100
- reason_code=$(cat ${KASEKI_RESULTS_DIR}/scouting-validation-reason.txt 2>/dev/null || echo "")
4103
+ reason_code=$(cat "${KASEKI_RESULTS_DIR}"/scouting-validation-reason.txt 2>/dev/null || echo "")
4101
4104
  case "$reason_code" in
4102
4105
  valid)
4103
4106
  # This shouldn't happen when exit_code=86, but just in case
@@ -4232,47 +4235,47 @@ run_scouting_agent() {
4232
4235
  printf '\n==> pi scouting agent\n'
4233
4236
  set_current_stage "pi scouting agent"
4234
4237
  if [ "$KASEKI_SCOUTING" = "0" ]; then
4235
- printf 'Pi scouting agent skipped because KASEKI_SCOUTING=0.\n' | tee -a ${KASEKI_RESULTS_DIR}/scouting-stderr.log
4238
+ printf 'Pi scouting agent skipped because KASEKI_SCOUTING=0.\n' | tee -a "${KASEKI_RESULTS_DIR}"/scouting-stderr.log
4236
4239
  record_stage_timing "pi scouting agent" 0 0 "skipped_by_config"
4237
4240
  return 0
4238
4241
  fi
4239
4242
  if [ "$KASEKI_DRY_RUN" = "1" ]; then
4240
- printf 'DRY-RUN: Pi scouting agent would inspect the task before coding.\n' | tee -a ${KASEKI_RESULTS_DIR}/scouting-stderr.log
4243
+ printf 'DRY-RUN: Pi scouting agent would inspect the task before coding.\n' | tee -a "${KASEKI_RESULTS_DIR}"/scouting-stderr.log
4241
4244
  record_stage_timing "pi scouting agent" 0 0 "dry_run=true"
4242
4245
  return 0
4243
4246
  fi
4244
4247
 
4245
4248
  scouting_prompt="$(build_scouting_prompt)"
4246
4249
  scouting_start="$(date +%s)"
4247
- scout_dirty_before="$(git status --porcelain 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || true)"
4248
- chmod -R a-w ${KASEKI_WORKSPACE_DIR}/repo 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || true
4250
+ scout_dirty_before="$(git status --porcelain 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || true)"
4251
+ chmod -R a-w "${KASEKI_WORKSPACE_DIR}"/repo 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || true
4249
4252
  set +e
4250
4253
  OPENROUTER_API_KEY="$openrouter_api_key" \
4251
4254
  timeout --signal=SIGTERM "$KASEKI_SCOUTING_TIMEOUT_SECONDS" \
4252
4255
  pi --mode json --no-session --provider "$KASEKI_PROVIDER" --model "$KASEKI_SCOUTING_MODEL" "$scouting_prompt" \
4253
- 2> >(tee -a ${KASEKI_RESULTS_DIR}/scouting-stderr.log >&2) \
4256
+ 2> >(tee -a "${KASEKI_RESULTS_DIR}"/scouting-stderr.log >&2) \
4254
4257
  | tee "$SCOUTING_RAW_EVENTS" \
4255
- | kaseki-pi-progress-stream ${KASEKI_RESULTS_DIR}/progress.jsonl ${KASEKI_RESULTS_DIR}/progress.log
4258
+ | kaseki-pi-progress-stream "${KASEKI_RESULTS_DIR}"/progress.jsonl "${KASEKI_RESULTS_DIR}"/progress.log
4256
4259
  SCOUTING_EXIT="${PIPESTATUS[0]}"
4257
4260
  SCOUTING_DURATION_SECONDS=$(($(date +%s) - scouting_start))
4258
4261
  unset scouting_prompt
4259
4262
  set +e
4260
- chmod -R u+w ${KASEKI_WORKSPACE_DIR}/repo 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || true
4263
+ chmod -R u+w "${KASEKI_WORKSPACE_DIR}"/repo 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || true
4261
4264
 
4262
4265
  if [ "$SCOUTING_EXIT" -eq 0 ] && ! validate_scouting_artifact "$SCOUTING_CANDIDATE_ARTIFACT" "$SCOUTING_ARTIFACT" "${KASEKI_RESULTS_DIR}/scouting-validation-reason.txt"; then
4263
4266
  SCOUTING_EXIT=86
4264
- scouting_validation_summary="$(cat ${KASEKI_RESULTS_DIR}/scouting-validation-summary.txt 2>/dev/null || printf 'scouting artifact validation failed')"
4265
- emit_error_event "pi_scouting_artifact_invalid" "Pi scouting handoff invalid: $scouting_validation_summary (full details: ${KASEKI_RESULTS_DIR}/scouting-validation-errors.jsonl)" "exit"
4267
+ scouting_validation_summary="$(cat "${KASEKI_RESULTS_DIR}"/scouting-validation-summary.txt 2>/dev/null || printf 'scouting artifact validation failed')"
4268
+ emit_error_event "pi_scouting_artifact_invalid" "Pi scouting handoff invalid: $scouting_validation_summary (full details: "${KASEKI_RESULTS_DIR}"/scouting-validation-errors.jsonl)" "exit"
4266
4269
  fi
4267
- scout_dirty_after="$(git status --porcelain 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || true)"
4270
+ scout_dirty_after="$(git status --porcelain 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || true)"
4268
4271
  if [ "$SCOUTING_EXIT" -eq 0 ] && [ "$scout_dirty_before" != "$scout_dirty_after" ]; then
4269
4272
  SCOUTING_EXIT=86
4270
4273
  emit_error_event "pi_scouting_workspace_modified" "Read-only scouting changed repository state before coding" "exit"
4271
4274
  fi
4272
4275
  rm -f "$SCOUTING_CANDIDATE_ARTIFACT"
4273
- git reset --hard -q HEAD 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || true
4274
- git clean -fd -q 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || true
4275
- kaseki-pi-event-filter "$SCOUTING_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/scouting-events.jsonl ${KASEKI_RESULTS_DIR}/scouting-summary.json 2>> ${KASEKI_RESULTS_DIR}/scouting-stderr.log || cp "$SCOUTING_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/scouting-events.raw.jsonl 2>/dev/null || true
4276
+ git reset --hard -q HEAD 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || true
4277
+ git clean -fd -q 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || true
4278
+ kaseki-pi-event-filter "$SCOUTING_RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/scouting-events.jsonl ${KASEKI_RESULTS_DIR}/scouting-summary.json 2>> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log || cp "$SCOUTING_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/scouting-events.raw.jsonl 2>/dev/null || true
4276
4279
  SCOUTING_ACTUAL_MODEL="$(node -e 'try { const s=require(process.env.KASEKI_RESULTS_DIR + "/scouting-summary.json"); const v=String(s.selected_model || s.model || "").trim(); console.log(v && v !== "unknown" && v !== "null" ? v : "unknown"); } catch { console.log("unknown"); }' 2>/dev/null)"
4277
4280
  record_stage_timing "pi scouting agent" "$SCOUTING_EXIT" "$SCOUTING_DURATION_SECONDS" "artifact=$SCOUTING_ARTIFACT timeout_seconds=$KASEKI_SCOUTING_TIMEOUT_SECONDS"
4278
4281
  if [ "$SCOUTING_EXIT" -ne 0 ]; then
@@ -4283,7 +4286,7 @@ run_scouting_agent() {
4283
4286
  fi
4284
4287
  emit_progress "pi scouting agent" "wrote scouting artifact"
4285
4288
  # Clean up validation reason file on success
4286
- rm -f ${KASEKI_RESULTS_DIR}/scouting-validation-reason.txt 2>/dev/null || true
4289
+ rm -f "${KASEKI_RESULTS_DIR}"/scouting-validation-reason.txt 2>/dev/null || true
4287
4290
  return 0
4288
4291
  }
4289
4292
 
@@ -4311,7 +4314,7 @@ run_scouting_agent_with_retry() {
4311
4314
  set -e
4312
4315
 
4313
4316
  # Append stderr to results for logging
4314
- cat "$scouting_stderr_capture" >> ${KASEKI_RESULTS_DIR}/scouting-stderr.log 2>/dev/null || true
4317
+ cat "$scouting_stderr_capture" >> "${KASEKI_RESULTS_DIR}"/scouting-stderr.log 2>/dev/null || true
4315
4318
  scouting_last_stderr="$(cat "$scouting_stderr_capture" 2>/dev/null || true)"
4316
4319
  rm -f "$scouting_stderr_capture"
4317
4320
 
@@ -4330,7 +4333,7 @@ run_scouting_agent_with_retry() {
4330
4333
  # Reset scouting artifacts for retry
4331
4334
  rm -f "$SCOUTING_ARTIFACT" "$SCOUTING_RAW_EVENTS" 2>/dev/null || true
4332
4335
  # Clean up validation reason file from previous attempt
4333
- rm -f ${KASEKI_RESULTS_DIR}/scouting-validation-reason.txt 2>/dev/null || true
4336
+ rm -f "${KASEKI_RESULTS_DIR}"/scouting-validation-reason.txt 2>/dev/null || true
4334
4337
  continue
4335
4338
  fi
4336
4339
  else
@@ -4368,7 +4371,7 @@ snapshot_attempt_artifacts() {
4368
4371
  collect_goal_check_feedback() {
4369
4372
  local instance_name="$1"
4370
4373
  local goal_setting_path="$GOAL_SETTING_ARTIFACT"
4371
- local results_dir="${KASEKI_RESULTS_DIR:-${KASEKI_RESULTS_DIR}}"
4374
+ local results_dir="$KASEKI_RESULTS_DIR"
4372
4375
  local goal_check_path="$results_dir/goal-check.json"
4373
4376
  local metadata_path="$results_dir/metadata.json"
4374
4377
  local feedback_file="$results_dir/goal-feedback.jsonl"
@@ -4379,7 +4382,7 @@ collect_goal_check_feedback() {
4379
4382
  fi
4380
4383
 
4381
4384
  # Use node script to collect feedback, append as JSONL
4382
- node "$SCRIPT_DIR/collect-feedback.js" goal-check "$instance_name" "$goal_setting_path" "$goal_check_path" "$metadata_path" 2>/dev/null | tee -a "$feedback_file" >/dev/null || true
4385
+ node "$SCRIPT_DIR/scripts/collect-feedback.js" goal-check "$instance_name" "$goal_setting_path" "$goal_check_path" "$metadata_path" 2>/dev/null | tee -a "$feedback_file" >/dev/null || true
4383
4386
  }
4384
4387
 
4385
4388
  collect_run_evaluation_feedback() {
@@ -4394,20 +4397,20 @@ collect_run_evaluation_feedback() {
4394
4397
  fi
4395
4398
 
4396
4399
  # Use node script to collect feedback, append as JSONL
4397
- node "$SCRIPT_DIR/collect-feedback.js" run-evaluation "$instance_name" "$run_evaluation_path" "$metadata_path" 2>/dev/null | tee -a "$feedback_file" >/dev/null || true
4400
+ node "$SCRIPT_DIR/scripts/collect-feedback.js" run-evaluation "$instance_name" "$run_evaluation_path" "$metadata_path" 2>/dev/null | tee -a "$feedback_file" >/dev/null || true
4398
4401
  }
4399
4402
 
4400
4403
 
4401
4404
  build_goal_check_prompt() {
4402
4405
  local validation_tail progress_tail goal_setting_context validation_context test_impact_context causality_context
4403
- validation_tail="$(tail -80 ${KASEKI_RESULTS_DIR}/validation.log 2>/dev/null || true)"
4406
+ validation_tail="$(tail -80 "${KASEKI_RESULTS_DIR}"/validation.log 2>/dev/null || true)"
4404
4407
  if [ -n "$(printf '%s' "$validation_tail" | tr -d '[:space:]')" ]; then
4405
4408
  validation_context="Validation log tail (last 80 lines):
4406
4409
  $validation_tail"
4407
4410
  else
4408
- validation_context="Validation log: ${KASEKI_RESULTS_DIR}/validation.log is empty or has not been produced yet. Treat validation logs as optional evidence for this pre-validation check; rely on the goal-setting output, scouting output, changed files, and git diff to determine whether the goal requirements are satisfied."
4411
+ validation_context="Validation log: "${KASEKI_RESULTS_DIR}"/validation.log is empty or has not been produced yet. Treat validation logs as optional evidence for this pre-validation check; rely on the goal-setting output, scouting output, changed files, and git diff to determine whether the goal requirements are satisfied."
4409
4412
  fi
4410
- progress_tail="$(tail -80 ${KASEKI_RESULTS_DIR}/progress.log 2>/dev/null || true)"
4413
+ progress_tail="$(tail -80 "${KASEKI_RESULTS_DIR}"/progress.log 2>/dev/null || true)"
4411
4414
  if [ -s "$TEST_IMPACT_WARNINGS_ARTIFACT" ]; then
4412
4415
  test_impact_context="Static test-impact warnings artifact ($TEST_IMPACT_WARNINGS_ARTIFACT):
4413
4416
  $(cat "$TEST_IMPACT_WARNINGS_ARTIFACT" 2>/dev/null)
@@ -4433,7 +4436,7 @@ $(head -n 200 "$GOAL_SETTING_ARTIFACT" 2>/dev/null)
4433
4436
  fi
4434
4437
 
4435
4438
  # Include causality assessment if available (helps interpret validation failures)
4436
- if [ -f ${KASEKI_RESULTS_DIR}/validation-causality-analysis.json ]; then
4439
+ if [ -f "${KASEKI_RESULTS_DIR}"/validation-causality-analysis.json ]; then
4437
4440
  # shellcheck disable=SC2016
4438
4441
  causality_context="VALIDATION FAILURE CAUSALITY ASSESSMENT:
4439
4442
 
@@ -4493,11 +4496,11 @@ Determine if the agent successfully met the requirements specified in the goal-s
4493
4496
 
4494
4497
  **Agent Artifacts** (use to verify requirements were met):
4495
4498
  - Scouting report: $SCOUTING_ARTIFACT
4496
- - Changed files: ${KASEKI_RESULTS_DIR}/changed-files.txt
4497
- - Git diff: ${KASEKI_RESULTS_DIR}/git.diff
4498
- - Validation outcomes (optional evidence; may be absent during pre-validation checks): ${KASEKI_RESULTS_DIR}/validation-timings.tsv and ${KASEKI_RESULTS_DIR}/validation.log
4499
+ - Changed files: "${KASEKI_RESULTS_DIR}"/changed-files.txt
4500
+ - Git diff: "${KASEKI_RESULTS_DIR}"/git.diff
4501
+ - Validation outcomes (optional evidence; may be absent during pre-validation checks): "${KASEKI_RESULTS_DIR}"/validation-timings.tsv and ${KASEKI_RESULTS_DIR}/validation.log
4499
4502
  - Static test-impact warnings (non-blocking): $TEST_IMPACT_WARNINGS_ARTIFACT
4500
- - Coding-agent events: ${KASEKI_RESULTS_DIR}/pi-summary.json and ${KASEKI_RESULTS_DIR}/pi-events.jsonl
4503
+ - Coding-agent events: "${KASEKI_RESULTS_DIR}"/pi-summary.json and ${KASEKI_RESULTS_DIR}/pi-events.jsonl
4501
4504
 
4502
4505
  ## Evaluation Framework: SMART Criteria Check
4503
4506
 
@@ -4556,7 +4559,7 @@ Example: "Null handling is done (parseRole returns 'Unnamed Role'), but test cov
4556
4559
  - Do not print, inspect, or expose environment variables, secrets, credentials, API keys, or mounted secret files.
4557
4560
  - Decide whether the goal requirements were realized. Do not evaluate code style, architecture, or elegance.
4558
4561
  - If anti-patterns were specified in goal-setting (do_not_modify, do_not_break), verify they were respected.
4559
- - Validation logs are optional evidence. If ${KASEKI_RESULTS_DIR}/validation.log is empty or absent, do not fail solely because validation evidence is unavailable; rely on goal-setting output, scouting output, changed files, and git diff.
4562
+ - Validation logs are optional evidence. If "${KASEKI_RESULTS_DIR}"/validation.log is empty or absent, do not fail solely because validation evidence is unavailable; rely on goal-setting output, scouting output, changed files, and git diff.
4560
4563
 
4561
4564
  ## Required JSON artifact
4562
4565
 
@@ -4607,12 +4610,12 @@ run_goal_check() {
4607
4610
  printf '\n==> goal check\n'
4608
4611
  set_current_stage "goal check"
4609
4612
  if [ "$KASEKI_GOAL_CHECK" != "1" ]; then
4610
- printf 'Goal check skipped because KASEKI_GOAL_CHECK=%s.\n' "$KASEKI_GOAL_CHECK" | tee -a ${KASEKI_RESULTS_DIR}/goal-check-stderr.log
4613
+ printf 'Goal check skipped because KASEKI_GOAL_CHECK=%s.\n' "$KASEKI_GOAL_CHECK" | tee -a "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log
4611
4614
  record_stage_timing "goal check" 0 0 "skipped_by_config attempt=$attempt"
4612
4615
  return 0
4613
4616
  fi
4614
4617
  if [ ! -s "$SCOUTING_ARTIFACT" ]; then
4615
- printf 'Goal check skipped because scouting artifact is unavailable.\n' | tee -a ${KASEKI_RESULTS_DIR}/goal-check-stderr.log
4618
+ printf 'Goal check skipped because scouting artifact is unavailable.\n' | tee -a "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log
4616
4619
  record_stage_timing "goal check" 0 0 "skipped_no_scouting attempt=$attempt"
4617
4620
  return 0
4618
4621
  fi
@@ -4623,15 +4626,15 @@ run_goal_check() {
4623
4626
  OPENROUTER_API_KEY="$openrouter_api_key" \
4624
4627
  timeout --signal=SIGTERM "$KASEKI_GOAL_CHECK_TIMEOUT_SECONDS" \
4625
4628
  pi --mode json --no-session --provider "$KASEKI_PROVIDER" --model "$KASEKI_GOAL_CHECK_MODEL" "$goal_prompt" \
4626
- 2> >(tee -a ${KASEKI_RESULTS_DIR}/goal-check-stderr.log >&2) \
4629
+ 2> >(tee -a "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log >&2) \
4627
4630
  | tee "$GOAL_CHECK_RAW_EVENTS" \
4628
- | kaseki-pi-progress-stream ${KASEKI_RESULTS_DIR}/progress.jsonl ${KASEKI_RESULTS_DIR}/progress.log
4631
+ | kaseki-pi-progress-stream "${KASEKI_RESULTS_DIR}"/progress.jsonl "${KASEKI_RESULTS_DIR}"/progress.log
4629
4632
  GOAL_CHECK_EXIT="${PIPESTATUS[0]}"
4630
4633
  unset goal_prompt
4631
4634
  GOAL_CHECK_DURATION_SECONDS=$((GOAL_CHECK_DURATION_SECONDS + $(date +%s) - goal_start))
4632
4635
  set +e
4633
4636
 
4634
- kaseki-pi-event-filter "$GOAL_CHECK_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/goal-check-events.jsonl ${KASEKI_RESULTS_DIR}/goal-check-summary.json 2>> ${KASEKI_RESULTS_DIR}/goal-check-stderr.log || true
4637
+ kaseki-pi-event-filter "$GOAL_CHECK_RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/goal-check-events.jsonl ${KASEKI_RESULTS_DIR}/goal-check-summary.json 2>> "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log || true
4635
4638
 
4636
4639
  if [ "$GOAL_CHECK_EXIT" -eq 0 ] && [ ! -f "$GOAL_CHECK_CANDIDATE_ARTIFACT" ]; then
4637
4640
  # Recover from goal-check agents that printed the verdict in assistant text instead of writing the artifact.
@@ -4731,25 +4734,25 @@ if (valid.size === 1) {
4731
4734
  const note = { timestamp: new Date().toISOString(), attempt, event: "goal_check_artifact_recovered_from_assistant_text", artifact: candidatePath, raw_events: rawPath, filtered_events: filteredPath };
4732
4735
  fs.appendFileSync(process.env.KASEKI_RESULTS_DIR + "/goal-check-stderr.log", JSON.stringify(note) + "\n");
4733
4736
  }
4734
- ' "$GOAL_CHECK_CANDIDATE_ARTIFACT" "$GOAL_CHECK_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/goal-check-events.jsonl "$attempt" 2>> ${KASEKI_RESULTS_DIR}/goal-check-stderr.log || true
4737
+ ' "$GOAL_CHECK_CANDIDATE_ARTIFACT" "$GOAL_CHECK_RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/goal-check-events.jsonl "$attempt" 2>> "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log || true
4735
4738
  fi
4736
4739
 
4737
- if [ "$GOAL_CHECK_EXIT" -eq 0 ] && ! validate_goal_check_artifact "$GOAL_CHECK_CANDIDATE_ARTIFACT" ${KASEKI_RESULTS_DIR}/goal-check.json "$attempt" ${KASEKI_RESULTS_DIR}/goal-check-validation-reason.txt; then
4740
+ if [ "$GOAL_CHECK_EXIT" -eq 0 ] && ! validate_goal_check_artifact "$GOAL_CHECK_CANDIDATE_ARTIFACT" "${KASEKI_RESULTS_DIR}"/goal-check.json "$attempt" ${KASEKI_RESULTS_DIR}/goal-check-validation-reason.txt; then
4738
4741
  GOAL_CHECK_EXIT=86
4739
- goal_check_validation_reason="$(cat ${KASEKI_RESULTS_DIR}/goal-check-validation-reason.txt 2>/dev/null || printf 'schema_mismatch')"
4740
- goal_check_validation_summary="$(cat ${KASEKI_RESULTS_DIR}/goal-check-validation-summary.txt 2>/dev/null || printf 'goal-check artifact validation failed')"
4742
+ goal_check_validation_reason="$(cat "${KASEKI_RESULTS_DIR}"/goal-check-validation-reason.txt 2>/dev/null || printf 'schema_mismatch')"
4743
+ goal_check_validation_summary="$(cat "${KASEKI_RESULTS_DIR}"/goal-check-validation-summary.txt 2>/dev/null || printf 'goal-check artifact validation failed')"
4741
4744
  case "$goal_check_validation_reason" in
4742
4745
  missing_file)
4743
4746
  GOAL_CHECK_FAILURE_REASON="goal_check_artifact_missing"
4744
- emit_error_event "goal_check_artifact_missing" "Goal-check candidate artifact was missing: $GOAL_CHECK_CANDIDATE_ARTIFACT ($goal_check_validation_summary; full details: ${KASEKI_RESULTS_DIR}/goal-check-validation-errors.jsonl)" "continue"
4747
+ emit_error_event "goal_check_artifact_missing" "Goal-check candidate artifact was missing: $GOAL_CHECK_CANDIDATE_ARTIFACT ($goal_check_validation_summary; full details: "${KASEKI_RESULTS_DIR}"/goal-check-validation-errors.jsonl)" "continue"
4745
4748
  ;;
4746
4749
  malformed_json)
4747
4750
  GOAL_CHECK_FAILURE_REASON="goal_check_artifact_malformed"
4748
- emit_error_event "goal_check_artifact_malformed" "Goal-check Pi wrote malformed JSON: $goal_check_validation_summary (full details: ${KASEKI_RESULTS_DIR}/goal-check-validation-errors.jsonl)" "continue"
4751
+ emit_error_event "goal_check_artifact_malformed" "Goal-check Pi wrote malformed JSON: $goal_check_validation_summary (full details: "${KASEKI_RESULTS_DIR}"/goal-check-validation-errors.jsonl)" "continue"
4749
4752
  ;;
4750
4753
  *)
4751
4754
  GOAL_CHECK_FAILURE_REASON="goal_check_artifact_invalid"
4752
- emit_error_event "goal_check_artifact_invalid" "Goal-check Pi did not write a schema-valid JSON verdict: $goal_check_validation_summary (full details: ${KASEKI_RESULTS_DIR}/goal-check-validation-errors.jsonl)" "continue"
4755
+ emit_error_event "goal_check_artifact_invalid" "Goal-check Pi did not write a schema-valid JSON verdict: $goal_check_validation_summary (full details: "${KASEKI_RESULTS_DIR}"/goal-check-validation-errors.jsonl)" "continue"
4753
4756
  ;;
4754
4757
  esac
4755
4758
  fi
@@ -4783,12 +4786,12 @@ if (valid.size === 1) {
4783
4786
 
4784
4787
  build_run_evaluation_prompt() {
4785
4788
  local validation_tail progress_tail stage_timings dependency_cache restoration_report draft_pr_body metadata_text goal_setting_context test_impact_context
4786
- validation_tail="$(tail -80 ${KASEKI_RESULTS_DIR}/validation.log 2>/dev/null || true)"
4787
- progress_tail="$(tail -80 ${KASEKI_RESULTS_DIR}/progress.log 2>/dev/null || true)"
4788
- stage_timings="$(tail -80 ${KASEKI_RESULTS_DIR}/stage-timings.tsv 2>/dev/null || true)"
4789
- dependency_cache="$(tail -80 ${KASEKI_RESULTS_DIR}/dependency-cache.log 2>/dev/null || true)"
4790
- restoration_report="$(tail -80 ${KASEKI_RESULTS_DIR}/restoration-report.md 2>/dev/null || true)"
4791
- metadata_text="$(cat ${KASEKI_RESULTS_DIR}/metadata.json 2>/dev/null || true)"
4789
+ validation_tail="$(tail -80 "${KASEKI_RESULTS_DIR}"/validation.log 2>/dev/null || true)"
4790
+ progress_tail="$(tail -80 "${KASEKI_RESULTS_DIR}"/progress.log 2>/dev/null || true)"
4791
+ stage_timings="$(tail -80 "${KASEKI_RESULTS_DIR}"/stage-timings.tsv 2>/dev/null || true)"
4792
+ dependency_cache="$(tail -80 "${KASEKI_RESULTS_DIR}"/dependency-cache.log 2>/dev/null || true)"
4793
+ restoration_report="$(tail -80 "${KASEKI_RESULTS_DIR}"/restoration-report.md 2>/dev/null || true)"
4794
+ metadata_text="$(cat "${KASEKI_RESULTS_DIR}"/metadata.json 2>/dev/null || true)"
4792
4795
  draft_pr_body="$(build_pr_body)"
4793
4796
  if [ -s "$TEST_IMPACT_WARNINGS_ARTIFACT" ]; then
4794
4797
  test_impact_context="Static test-impact warnings artifact ($TEST_IMPACT_WARNINGS_ARTIFACT):
@@ -4834,15 +4837,15 @@ This is NOT another goal-check. The goal-check evaluator already determined if t
4834
4837
  - Quality metrics, SMART criteria, anti-patterns
4835
4838
 
4836
4839
  **Agent Artifacts** (verify goal was realized):
4837
- - Goal-check verdict: ${KASEKI_RESULTS_DIR}/goal-check.json
4838
- - Scouting report: ${KASEKI_RESULTS_DIR}/scouting.json
4839
- - Changed files: ${KASEKI_RESULTS_DIR}/changed-files.txt
4840
- - Git diff and status: ${KASEKI_RESULTS_DIR}/git.diff, ${KASEKI_RESULTS_DIR}/git.status
4841
- - Validation timings/logs: ${KASEKI_RESULTS_DIR}/pre-validation-timings.tsv, ${KASEKI_RESULTS_DIR}/validation-timings.tsv, ${KASEKI_RESULTS_DIR}/validation.log
4840
+ - Goal-check verdict: "${KASEKI_RESULTS_DIR}"/goal-check.json
4841
+ - Scouting report: "${KASEKI_RESULTS_DIR}"/scouting.json
4842
+ - Changed files: "${KASEKI_RESULTS_DIR}"/changed-files.txt
4843
+ - Git diff and status: "${KASEKI_RESULTS_DIR}"/git.diff, ${KASEKI_RESULTS_DIR}/git.status
4844
+ - Validation timings/logs: "${KASEKI_RESULTS_DIR}"/pre-validation-timings.tsv, ${KASEKI_RESULTS_DIR}/validation-timings.tsv, ${KASEKI_RESULTS_DIR}/validation.log
4842
4845
  - Static test-impact warnings (non-blocking): $TEST_IMPACT_WARNINGS_ARTIFACT
4843
- - Stage timings: ${KASEKI_RESULTS_DIR}/stage-timings.tsv
4844
- - Progress log: ${KASEKI_RESULTS_DIR}/progress.log
4845
- - Metadata: ${KASEKI_RESULTS_DIR}/metadata.json
4846
+ - Stage timings: "${KASEKI_RESULTS_DIR}"/stage-timings.tsv
4847
+ - Progress log: "${KASEKI_RESULTS_DIR}"/progress.log
4848
+ - Metadata: "${KASEKI_RESULTS_DIR}"/metadata.json
4846
4849
 
4847
4850
  ## Evaluation Framework
4848
4851
 
@@ -5056,12 +5059,12 @@ run_run_evaluation() {
5056
5059
  printf '\n==> run evaluation\n'
5057
5060
  set_current_stage "run evaluation"
5058
5061
  if [ "$KASEKI_RUN_EVALUATION" != "1" ]; then
5059
- printf 'Run evaluation skipped because KASEKI_RUN_EVALUATION=%s.\n' "$KASEKI_RUN_EVALUATION" | tee -a ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log
5062
+ printf 'Run evaluation skipped because KASEKI_RUN_EVALUATION=%s.\n' "$KASEKI_RUN_EVALUATION" | tee -a "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log
5060
5063
  record_stage_timing "run evaluation" 0 0 "skipped_by_config"
5061
5064
  return 0
5062
5065
  fi
5063
5066
  if [ "$KASEKI_DRY_RUN" = "1" ]; then
5064
- printf 'Run evaluation skipped for dry-run/startup-check mode.\n' | tee -a ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log
5067
+ printf 'Run evaluation skipped for dry-run/startup-check mode.\n' | tee -a "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log
5065
5068
  record_stage_timing "run evaluation" 0 0 "dry_run=true"
5066
5069
  return 0
5067
5070
  fi
@@ -5070,19 +5073,19 @@ run_run_evaluation() {
5070
5073
  write_metadata "$STATUS"
5071
5074
  evaluation_prompt="$(build_run_evaluation_prompt)"
5072
5075
  evaluation_start="$(date +%s)"
5073
- eval_dirty_before="$(git status --porcelain 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true)"
5074
- chmod -R a-w ${KASEKI_WORKSPACE_DIR}/repo 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true
5076
+ eval_dirty_before="$(git status --porcelain 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true)"
5077
+ chmod -R a-w "${KASEKI_WORKSPACE_DIR}"/repo 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true
5075
5078
  set +e
5076
5079
  OPENROUTER_API_KEY="$openrouter_api_key" \
5077
5080
  timeout --signal=SIGTERM "$KASEKI_RUN_EVALUATION_TIMEOUT_SECONDS" \
5078
5081
  pi --mode json --no-session --provider "$KASEKI_PROVIDER" --model "$KASEKI_RUN_EVALUATION_MODEL" "$evaluation_prompt" \
5079
- 2> >(tee -a ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log >&2) \
5082
+ 2> >(tee -a "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log >&2) \
5080
5083
  | tee "$RUN_EVALUATION_RAW_EVENTS" \
5081
- | kaseki-pi-progress-stream ${KASEKI_RESULTS_DIR}/progress.jsonl ${KASEKI_RESULTS_DIR}/progress.log
5084
+ | kaseki-pi-progress-stream "${KASEKI_RESULTS_DIR}"/progress.jsonl "${KASEKI_RESULTS_DIR}"/progress.log
5082
5085
  RUN_EVALUATION_EXIT="${PIPESTATUS[0]}"
5083
5086
  unset evaluation_prompt
5084
5087
  RUN_EVALUATION_DURATION_SECONDS=$((RUN_EVALUATION_DURATION_SECONDS + $(date +%s) - evaluation_start))
5085
- chmod -R u+w ${KASEKI_WORKSPACE_DIR}/repo 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true
5088
+ chmod -R u+w "${KASEKI_WORKSPACE_DIR}"/repo 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true
5086
5089
  set +e
5087
5090
 
5088
5091
  if [ "$RUN_EVALUATION_EXIT" -eq 0 ] && ! node -e '
@@ -5112,15 +5115,15 @@ artifact.timestamp = new Date().toISOString();
5112
5115
  artifact.model = model;
5113
5116
  artifact.actual_model = actualModel;
5114
5117
  fs.writeFileSync(output, JSON.stringify(artifact, null, 2) + "\n");
5115
- ' "$RUN_EVALUATION_CANDIDATE_ARTIFACT" "$RUN_EVALUATION_ARTIFACT" "$KASEKI_RUN_EVALUATION_MODEL" "$RUN_EVALUATION_ACTUAL_MODEL" 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log; then
5118
+ ' "$RUN_EVALUATION_CANDIDATE_ARTIFACT" "$RUN_EVALUATION_ARTIFACT" "$KASEKI_RUN_EVALUATION_MODEL" "$RUN_EVALUATION_ACTUAL_MODEL" 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log; then
5116
5119
  RUN_EVALUATION_EXIT=86
5117
5120
  emit_error_event "run_evaluation_artifact_invalid" "Run-evaluation Pi did not write a schema-valid JSON artifact" "continue"
5118
5121
  fi
5119
5122
  rm -f "$RUN_EVALUATION_CANDIDATE_ARTIFACT"
5120
- kaseki-pi-event-filter "$RUN_EVALUATION_RAW_EVENTS" ${KASEKI_RESULTS_DIR}/run-evaluation-events.jsonl ${KASEKI_RESULTS_DIR}/run-evaluation-summary.json 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true
5123
+ kaseki-pi-event-filter "$RUN_EVALUATION_RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/run-evaluation-events.jsonl ${KASEKI_RESULTS_DIR}/run-evaluation-summary.json 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true
5121
5124
  RUN_EVALUATION_ACTUAL_MODEL="$(node -e 'try { const s=require(process.env.KASEKI_RESULTS_DIR + "/run-evaluation-summary.json"); const v=String(s.selected_model || s.model || "").trim(); console.log(v && v !== "unknown" && v !== "null" ? v : "unknown"); } catch { console.log("unknown"); }' 2>/dev/null)"
5122
5125
  if [ -s "$RUN_EVALUATION_ARTIFACT" ]; then
5123
- node - "$RUN_EVALUATION_ARTIFACT" "$RUN_EVALUATION_ACTUAL_MODEL" <<'NODE' 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true
5126
+ node - "$RUN_EVALUATION_ARTIFACT" "$RUN_EVALUATION_ACTUAL_MODEL" <<'NODE' 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true
5124
5127
  const fs = require('fs');
5125
5128
  const [file, actualModel] = process.argv.slice(2);
5126
5129
  const artifact = JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -5129,12 +5132,12 @@ fs.writeFileSync(file, JSON.stringify(artifact, null, 2) + '\n');
5129
5132
  NODE
5130
5133
  fi
5131
5134
 
5132
- eval_dirty_after="$(git status --porcelain 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true)"
5135
+ eval_dirty_after="$(git status --porcelain 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true)"
5133
5136
  if [ "$eval_dirty_before" != "$eval_dirty_after" ]; then
5134
5137
  RUN_EVALUATION_EXIT=86
5135
5138
  emit_error_event "run_evaluation_workspace_modified" "Read-only run evaluation changed repository state; restoring workspace" "continue"
5136
- git reset --hard -q HEAD 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true
5137
- git clean -fd -q 2>> ${KASEKI_RESULTS_DIR}/run-evaluation-stderr.log || true
5139
+ git reset --hard -q HEAD 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true
5140
+ git clean -fd -q 2>> "${KASEKI_RESULTS_DIR}"/run-evaluation-stderr.log || true
5138
5141
  fi
5139
5142
 
5140
5143
  if [ "$RUN_EVALUATION_EXIT" -ne 0 ] || [ ! -s "$RUN_EVALUATION_ARTIFACT" ]; then
@@ -5995,7 +5998,7 @@ derive_pr_title() {
5995
5998
 
5996
5999
  candidate="$(printf '%s' "${TASK_PROMPT:-}" | sanitize_pr_metadata_text)"
5997
6000
  prompt_for_prefix="$candidate"
5998
- if [ -s ${KASEKI_RESULTS_DIR}/result-summary.md ]; then
6001
+ if [ -s "${KASEKI_RESULTS_DIR}"/result-summary.md ]; then
5999
6002
  summary_candidate="$(
6000
6003
  awk '
6001
6004
  /^##[[:space:]]+Summary[[:space:]]*$/ { in_summary=1; next }
@@ -6006,13 +6009,13 @@ derive_pr_title() {
6006
6009
  sub(/^[[:space:]]*[0-9]+[.)][[:space:]]+/, "", line)
6007
6010
  if (line !~ /^[[:space:]]*$/) { print line; exit }
6008
6011
  }
6009
- ' ${KASEKI_RESULTS_DIR}/result-summary.md 2>/dev/null | sanitize_pr_metadata_text
6012
+ ' "${KASEKI_RESULTS_DIR}"/result-summary.md 2>/dev/null | sanitize_pr_metadata_text
6010
6013
  )"
6011
6014
  fi
6012
6015
  if [ -n "$summary_candidate" ]; then
6013
6016
  candidate="$summary_candidate"
6014
- elif [ -z "$candidate" ] && [ -s ${KASEKI_RESULTS_DIR}/result-summary.md ]; then
6015
- candidate="$(sed -n '/^- Status:/p; /^- Changed files:/p; /^- Validation:/p' ${KASEKI_RESULTS_DIR}/result-summary.md 2>/dev/null | head -n 3 | sanitize_pr_metadata_text)"
6017
+ elif [ -z "$candidate" ] && [ -s "${KASEKI_RESULTS_DIR}"/result-summary.md ]; then
6018
+ candidate="$(sed -n '/^- Status:/p; /^- Changed files:/p; /^- Validation:/p' "${KASEKI_RESULTS_DIR}"/result-summary.md 2>/dev/null | head -n 3 | sanitize_pr_metadata_text)"
6016
6019
  fi
6017
6020
 
6018
6021
  candidate="$(printf '%s' "$candidate" | sed -E 's/^[[:space:]]*([0-9]+[.)]|[-*])[[:space:]]+//' | tr '\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/(^|[[:space:]])[0-9]+[.)][[:space:]]+/\1/g; s/(^|[[:space:]])[-*][[:space:]]+/\1/g; s/userfacing/user-facing/Ig; s/customerfacing/customer-facing/Ig; s/front[ -]?end/frontend/Ig; s/back[ -]?end/backend/Ig; s/full[ -]?stack/full-stack/Ig; s/^[[:space:]]+//; s/[[:space:]]+$//')"
@@ -6022,8 +6025,8 @@ derive_pr_title() {
6022
6025
  candidate="$stripped"
6023
6026
  fi
6024
6027
 
6025
- if [ -s ${KASEKI_RESULTS_DIR}/changed-files.txt ]; then
6026
- changed_files="$(sanitize_pr_metadata_text < ${KASEKI_RESULTS_DIR}/changed-files.txt || true)"
6028
+ if [ -s "${KASEKI_RESULTS_DIR}"/changed-files.txt ]; then
6029
+ changed_files="$(sanitize_pr_metadata_text < "${KASEKI_RESULTS_DIR}"/changed-files.txt || true)"
6027
6030
  else
6028
6031
  changed_files=""
6029
6032
  fi
@@ -6353,10 +6356,10 @@ build_pr_improvements_summary() {
6353
6356
  done < "$changed_files_file"
6354
6357
  fi
6355
6358
 
6356
- if [ -s ${KASEKI_RESULTS_DIR}/result-summary.md ]; then
6359
+ if [ -s "${KASEKI_RESULTS_DIR}"/result-summary.md ]; then
6357
6360
  summary_source="${KASEKI_RESULTS_DIR}/result-summary.md"
6358
6361
  else
6359
- for artifact in ${KASEKI_RESULTS_DIR}/analysis.md ${KASEKI_RESULTS_DIR}/pi-summary.json; do
6362
+ for artifact in "${KASEKI_RESULTS_DIR}"/analysis.md ${KASEKI_RESULTS_DIR}/pi-summary.json; do
6360
6363
  if [ -s "$artifact" ]; then
6361
6364
  summary_source="$artifact"
6362
6365
  break
@@ -6542,7 +6545,7 @@ $(build_pr_improvements_summary)
6542
6545
  ## Agent review
6543
6546
  $(build_pr_agent_review "$all_validation_statuses_pass")
6544
6547
 
6545
- $(if [ -s ${KASEKI_RESULTS_DIR}/run-evaluation.json ]; then printf '## Agent evaluation\n%s\n\n' "$(build_pr_agent_evaluation)"; fi)
6548
+ $(if [ -s "${KASEKI_RESULTS_DIR}"/run-evaluation.json ]; then printf '## Agent evaluation\n%s\n\n' "$(build_pr_agent_evaluation)"; fi)
6546
6549
  ## Validation
6547
6550
  ### Validation statuses
6548
6551
  - Pre-agent validation: $pre_validation_status
@@ -6591,11 +6594,11 @@ run_github_operations() {
6591
6594
  owner="$GITHUB_REPO_OWNER"
6592
6595
  repo="$GITHUB_REPO_NAME"
6593
6596
  else
6594
- printf -- 'Cannot parse GitHub repo URL: %s\n' "$REPO_URL" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6597
+ printf -- 'Cannot parse GitHub repo URL: %s\n' "$REPO_URL" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6595
6598
  return 7
6596
6599
  fi
6597
6600
 
6598
- printf -- 'GitHub operations: owner=%s, repo=%s\n' "$owner" "$repo" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6601
+ printf -- 'GitHub operations: owner=%s, repo=%s\n' "$owner" "$repo" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6599
6602
  GITHUB_OPERATION_PHASE="setup"
6600
6603
 
6601
6604
  # Set git user for commits
@@ -6604,7 +6607,7 @@ run_github_operations() {
6604
6607
 
6605
6608
  # Generate GitHub App installation token
6606
6609
  GITHUB_OPERATION_PHASE="token_generation"
6607
- printf 'Generating GitHub App installation token...\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6610
+ printf 'Generating GitHub App installation token...\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6608
6611
  local github_app_token_helper="${KASEKI_GITHUB_APP_TOKEN_HELPER:-/usr/local/bin/github-app-token}"
6609
6612
  local token_stdout_tmp token_stderr_tmp token_exit_code token_stderr token_parse_result token_error token_http_status
6610
6613
  token_stdout_tmp="$(mktemp /tmp/github-app-token-stdout.XXXXXX)" || { printf 'Failed to create token stdout temp file\n' >&2; return 7; }
@@ -6625,7 +6628,7 @@ run_github_operations() {
6625
6628
  if [ "$token_parse_result" != "$token_error" ]; then
6626
6629
  token_http_status="${token_parse_result#*$'\t'}"
6627
6630
  fi
6628
- printf 'Failed to generate token: %s\n' "$token_error" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6631
+ printf 'Failed to generate token: %s\n' "$token_error" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6629
6632
  GITHUB_API_ERROR_TYPE="github_app_token_error"
6630
6633
  GITHUB_API_ERROR_MESSAGE="$token_error"
6631
6634
  GITHUB_API_HTTP_STATUS="$token_http_status"
@@ -6635,83 +6638,83 @@ run_github_operations() {
6635
6638
  fi
6636
6639
 
6637
6640
  # Use helper to extract token from JSON response
6638
- if ! run_node_subprocess token "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); process.stdout.write(d.token || '')" "$token_data" ${KASEKI_RESULTS_DIR}/git-push.log; then
6639
- printf -- 'Failed to extract token from response: %s\n' "$token_data" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6641
+ if ! run_node_subprocess token "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); process.stdout.write(d.token || '')" "$token_data" "${KASEKI_RESULTS_DIR}"/git-push.log; then
6642
+ printf -- 'Failed to extract token from response: %s\n' "$token_data" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6640
6643
  GITHUB_PUSH_EXIT=7
6641
6644
  return 7
6642
6645
  fi
6643
6646
 
6644
6647
  if [ -z "$token" ]; then
6645
- printf -- 'Failed to extract token from response (empty result)\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6648
+ printf -- 'Failed to extract token from response (empty result)\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6646
6649
  GITHUB_PUSH_EXIT=7
6647
6650
  return 7
6648
6651
  fi
6649
6652
 
6650
- printf 'Token generated successfully\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6653
+ printf 'Token generated successfully\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6651
6654
 
6652
6655
  # Create and push feature branch
6653
6656
  GITHUB_OPERATION_PHASE="branch_creation"
6654
6657
  feature_branch="kaseki/$INSTANCE_NAME"
6655
- printf -- 'Creating feature branch: %s\n' "$feature_branch" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6658
+ printf -- 'Creating feature branch: %s\n' "$feature_branch" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6656
6659
  git checkout -b "$feature_branch" || {
6657
- printf 'Failed to create branch\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6660
+ printf 'Failed to create branch\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6658
6661
  GITHUB_PUSH_EXIT=7
6659
6662
  return 7
6660
6663
  }
6661
6664
 
6662
6665
  # Commit changes (git should already have changes from pi agent)
6663
6666
  GITHUB_OPERATION_PHASE="commit"
6664
- printf 'Committing changes...\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6665
- if [ ! -s ${KASEKI_RESULTS_DIR}/changed-files.txt ]; then
6666
- printf 'No changed files to stage\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6667
+ printf 'Committing changes...\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6668
+ if [ ! -s "${KASEKI_RESULTS_DIR}"/changed-files.txt ]; then
6669
+ printf 'No changed files to stage\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6667
6670
  GITHUB_PUSH_EXIT=7
6668
6671
  return 7
6669
6672
  fi
6670
6673
  while IFS= read -r changed_file || [ -n "$changed_file" ]; do
6671
6674
  [ -z "$changed_file" ] && continue
6672
6675
  git add -- "$changed_file" || {
6673
- printf -- 'Failed to stage changed file: %s\n' "$changed_file" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6676
+ printf -- 'Failed to stage changed file: %s\n' "$changed_file" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6674
6677
  GITHUB_PUSH_EXIT=7
6675
6678
  return 7
6676
6679
  }
6677
- done < ${KASEKI_RESULTS_DIR}/changed-files.txt
6680
+ done < "${KASEKI_RESULTS_DIR}"/changed-files.txt
6678
6681
  if ! git commit -m "Kaseki: $INSTANCE_NAME"; then
6679
- printf 'No changes to commit or commit failed\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6682
+ printf 'No changes to commit or commit failed\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6680
6683
  GITHUB_PUSH_EXIT=7
6681
6684
  return 7
6682
6685
  fi
6683
6686
 
6684
6687
  # Push branch
6685
6688
  GITHUB_OPERATION_PHASE="push"
6686
- printf 'Pushing branch to GitHub...\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6689
+ printf 'Pushing branch to GitHub...\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6687
6690
  local askpass_file
6688
- if ! create_github_askpass_helper ${KASEKI_RESULTS_DIR}/git-push.log 'GitHub credential helper'; then
6691
+ if ! create_github_askpass_helper "${KASEKI_RESULTS_DIR}"/git-push.log 'GitHub credential helper'; then
6689
6692
  return 8
6690
6693
  fi
6691
6694
  askpass_file="$GITHUB_ASKPASS_FILE"
6692
6695
 
6693
6696
  KASEKI_GITHUB_TOKEN="$token" GIT_ASKPASS="$askpass_file" GIT_TERMINAL_PROMPT=0 \
6694
- git push "https://github.com/$owner/$repo.git" "$feature_branch" --force-with-lease 2>&1 | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6697
+ git push "https://github.com/$owner/$repo.git" "$feature_branch" --force-with-lease 2>&1 | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6695
6698
  git_push_exit="${PIPESTATUS[0]:-1}"
6696
6699
  if [ "$git_push_exit" -eq 0 ]; then
6697
- printf 'Branch pushed successfully\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6700
+ printf 'Branch pushed successfully\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6698
6701
  else
6699
6702
  rm -f "$askpass_file"
6700
- printf 'Failed to push branch (exit %s)\n' "$git_push_exit" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6703
+ printf 'Failed to push branch (exit %s)\n' "$git_push_exit" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6701
6704
  GITHUB_PUSH_EXIT="$git_push_exit"
6702
6705
  return "$git_push_exit"
6703
6706
  fi
6704
6707
  rm -f "$askpass_file"
6705
6708
 
6706
6709
  if [ "$KASEKI_PUBLISH_MODE" = "branch" ]; then
6707
- printf 'Publish mode branch: skipping pull request creation.\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6710
+ printf 'Publish mode branch: skipping pull request creation.\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6708
6711
  GITHUB_PR_EXIT=0
6709
6712
  GITHUB_OPERATION_PHASE="completed"
6710
6713
  unset token
6711
6714
  return 0
6712
6715
  fi
6713
6716
  if ! is_pr_creation_mode; then
6714
- printf 'Publish mode %s: skipping pull request creation.\n' "$KASEKI_PUBLISH_MODE" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6717
+ printf 'Publish mode %s: skipping pull request creation.\n' "$KASEKI_PUBLISH_MODE" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6715
6718
  GITHUB_PR_EXIT=0
6716
6719
  GITHUB_OPERATION_PHASE="completed"
6717
6720
  unset token
@@ -6721,7 +6724,7 @@ run_github_operations() {
6721
6724
  # Create pull request. Both pr and draft_pr push a branch and create a PR;
6722
6725
  # only draft_pr marks the GitHub Pulls API request as draft.
6723
6726
  GITHUB_OPERATION_PHASE="pr_creation"
6724
- printf 'Creating pull request...\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6727
+ printf 'Creating pull request...\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6725
6728
  emit_progress "github operations" "pr_creation_starting"
6726
6729
  local pr_title pr_body pr_response pr_url pr_number pr_http_status pr_draft_json
6727
6730
  pr_title="$(derive_pr_title)"
@@ -6745,7 +6748,7 @@ run_github_operations() {
6745
6748
  - Generated at (UTC): $fallback_timestamp
6746
6749
  EOF
6747
6750
  )
6748
- printf 'WARN: build_pr_body returned empty content after sanitization; using fallback PR body.\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6751
+ printf 'WARN: build_pr_body returned empty content after sanitization; using fallback PR body.\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6749
6752
  fi
6750
6753
  if is_pr_draft_mode; then
6751
6754
  pr_draft_json=true
@@ -6759,7 +6762,7 @@ EOF
6759
6762
 
6760
6763
  while [ $retry_count -le "$max_retries" ]; do
6761
6764
  if [ $retry_count -gt 0 ]; then
6762
- printf 'Retrying PR creation (attempt %d of %d) after %ds delay...\n' $((retry_count + 1)) "$max_retries" "$backoff_delay" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6765
+ printf 'Retrying PR creation (attempt %d of %d) after %ds delay...\n' $((retry_count + 1)) "$max_retries" "$backoff_delay" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6763
6766
  emit_progress "github operations" "pr_creation_attempt $((retry_count + 1))/$max_retries"
6764
6767
  sleep "$backoff_delay"
6765
6768
  # Exponential backoff: 2s, 4s, 8s
@@ -6769,31 +6772,31 @@ EOF
6769
6772
 
6770
6773
  # Capture both response and HTTP status code
6771
6774
  local pr_response_file temp_status_file
6772
- pr_response_file="$(mktemp /tmp/kaseki-pr-response.XXXXXX)" || { printf 'Failed to create temp file for PR response\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2; GITHUB_PR_EXIT=8; return 8; }
6773
- temp_status_file="$(mktemp /tmp/kaseki-pr-status.XXXXXX)" || { printf 'Failed to create temp file for PR status\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2; GITHUB_PR_EXIT=8; return 8; }
6775
+ pr_response_file="$(mktemp /tmp/kaseki-pr-response.XXXXXX)" || { printf 'Failed to create temp file for PR response\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2; GITHUB_PR_EXIT=8; return 8; }
6776
+ temp_status_file="$(mktemp /tmp/kaseki-pr-status.XXXXXX)" || { printf 'Failed to create temp file for PR status\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2; GITHUB_PR_EXIT=8; return 8; }
6774
6777
 
6775
6778
  if [ $retry_count -eq 0 ] && [ "${KASEKI_DEBUG:-0}" = "1" ]; then
6776
- printf 'Debug: Creating PR with head=%s, base=%s, draft=%s\n' "$feature_branch" "$GIT_REF" "$pr_draft_json" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6779
+ printf 'Debug: Creating PR with head=%s, base=%s, draft=%s\n' "$feature_branch" "$GIT_REF" "$pr_draft_json" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6777
6780
  fi
6778
6781
 
6779
6782
  # Encode PR title and body as JSON strings
6780
6783
  local pr_title_json pr_body_json
6781
6784
  pr_title_json='""'
6782
6785
  pr_body_json='""'
6783
- if ! run_node_subprocess pr_title_json "console.log(JSON.stringify(require('fs').readFileSync(0, 'utf8')))" "$pr_title" ${KASEKI_RESULTS_DIR}/git-push.log; then
6784
- printf 'ERROR: Failed to JSON encode PR title\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6786
+ if ! run_node_subprocess pr_title_json "console.log(JSON.stringify(require('fs').readFileSync(0, 'utf8')))" "$pr_title" "${KASEKI_RESULTS_DIR}"/git-push.log; then
6787
+ printf 'ERROR: Failed to JSON encode PR title\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6785
6788
  GITHUB_PR_EXIT=8
6786
6789
  return 8
6787
6790
  fi
6788
- if ! run_node_subprocess pr_body_json "console.log(JSON.stringify(require('fs').readFileSync(0, 'utf8')))" "$pr_body" ${KASEKI_RESULTS_DIR}/git-push.log; then
6789
- printf 'ERROR: Failed to JSON encode PR body\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6791
+ if ! run_node_subprocess pr_body_json "console.log(JSON.stringify(require('fs').readFileSync(0, 'utf8')))" "$pr_body" "${KASEKI_RESULTS_DIR}"/git-push.log; then
6792
+ printf 'ERROR: Failed to JSON encode PR body\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6790
6793
  GITHUB_PR_EXIT=8
6791
6794
  return 8
6792
6795
  fi
6793
6796
 
6794
6797
  # Validate both variables are non-empty before using in curl
6795
6798
  if [ -z "$pr_title_json" ] || [ -z "$pr_body_json" ]; then
6796
- printf 'ERROR: JSON encoding produced empty values (title=%s, body=%s)\n' "$pr_title_json" "$pr_body_json" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6799
+ printf 'ERROR: JSON encoding produced empty values (title=%s, body=%s)\n' "$pr_title_json" "$pr_body_json" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6797
6800
  GITHUB_PR_EXIT=8
6798
6801
  return 8
6799
6802
  fi
@@ -6818,7 +6821,7 @@ EOF
6818
6821
 
6819
6822
  if [ $curl_exit -ne 0 ]; then
6820
6823
  # curl command itself failed (network error, timeout, etc.)
6821
- printf 'GitHub PR API curl command failed with exit code %d (attempt %d)\n' "$curl_exit" $((retry_count + 1)) | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6824
+ printf 'GitHub PR API curl command failed with exit code %d (attempt %d)\n' "$curl_exit" $((retry_count + 1)) | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6822
6825
  GITHUB_API_HTTP_STATUS="0"
6823
6826
  if is_github_pr_error_retryable "0" "curl_error" && [ "$retry_count" -lt "$((max_retries - 1))" ]; then
6824
6827
  retry_count=$((retry_count + 1))
@@ -6836,43 +6839,43 @@ EOF
6836
6839
  fi
6837
6840
 
6838
6841
  if [ "${KASEKI_DEBUG:-0}" = "1" ]; then
6839
- printf 'Debug: PR API response HTTP status: %s (attempt %d)\n' "$pr_http_status" $((retry_count + 1)) | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6842
+ printf 'Debug: PR API response HTTP status: %s (attempt %d)\n' "$pr_http_status" $((retry_count + 1)) | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6840
6843
  fi
6841
6844
 
6842
6845
  # Validate the API response
6843
- if validate_github_api_response "$pr_http_status" "$pr_response" ${KASEKI_RESULTS_DIR}/git-push.log; then
6846
+ if validate_github_api_response "$pr_http_status" "$pr_response" "${KASEKI_RESULTS_DIR}"/git-push.log; then
6844
6847
  # API returned success (201); now extract the URL and issue number using helper
6845
- if ! run_node_subprocess pr_url "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); process.stdout.write(d.html_url || '')" "$pr_response" ${KASEKI_RESULTS_DIR}/git-push.log; then
6846
- printf 'ERROR: Failed to extract PR URL from API response\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6848
+ if ! run_node_subprocess pr_url "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); process.stdout.write(d.html_url || '')" "$pr_response" "${KASEKI_RESULTS_DIR}"/git-push.log; then
6849
+ printf 'ERROR: Failed to extract PR URL from API response\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6847
6850
  emit_error_event "github_pr_response_malformed" "Failed to parse PR API response to extract html_url" "exit"
6848
6851
  GITHUB_PR_EXIT=9
6849
6852
  pr_url=""
6850
6853
  fi
6851
- if ! run_node_subprocess pr_number "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); if (Number.isInteger(d.number)) process.stdout.write(String(d.number));" "$pr_response" ${KASEKI_RESULTS_DIR}/git-push.log; then
6852
- printf 'Warning: failed to extract PR number from API response; leaving PR unlabeled\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6854
+ if ! run_node_subprocess pr_number "const d = JSON.parse(require('fs').readFileSync(0, 'utf8')); if (Number.isInteger(d.number)) process.stdout.write(String(d.number));" "$pr_response" "${KASEKI_RESULTS_DIR}"/git-push.log; then
6855
+ printf 'Warning: failed to extract PR number from API response; leaving PR unlabeled\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6853
6856
  pr_number=""
6854
6857
  fi
6855
6858
 
6856
6859
  if [ -n "$pr_url" ]; then
6857
6860
  GITHUB_PR_URL="$pr_url"
6858
6861
  GITHUB_PR_EXIT=0
6859
- printf 'Pull request created: %s\n' "$pr_url" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6862
+ printf 'Pull request created: %s\n' "$pr_url" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6860
6863
  if [ -n "$pr_number" ]; then
6861
- apply_github_pr_labels "$owner" "$repo" "$pr_number" "$token" ${KASEKI_RESULTS_DIR}/git-push.log || true
6864
+ apply_github_pr_labels "$owner" "$repo" "$pr_number" "$token" "${KASEKI_RESULTS_DIR}"/git-push.log || true
6862
6865
  # Request repository owner as reviewer for personal repos
6863
- request_owner_review "$pr_response" "$token" ${KASEKI_RESULTS_DIR}/git-push.log || true
6866
+ request_owner_review "$pr_response" "$token" "${KASEKI_RESULTS_DIR}"/git-push.log || true
6864
6867
  else
6865
- printf 'Warning: PR API response missing number field; leaving PR unlabeled\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6868
+ printf 'Warning: PR API response missing number field; leaving PR unlabeled\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6866
6869
  fi
6867
6870
  pr_created=1
6868
6871
  rm -f "$pr_response_file"
6869
6872
  break
6870
6873
  else
6871
6874
  # HTTP 201 but no html_url in response - malformed response
6872
- printf 'Pull request API returned success (201) but response missing html_url field\n' | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6875
+ printf 'Pull request API returned success (201) but response missing html_url field\n' | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6873
6876
  emit_error_event "github_pr_response_malformed" "GitHub PR API returned 201 but response missing html_url field" "exit"
6874
6877
  if [ "${KASEKI_DEBUG:-0}" = "1" ]; then
6875
- printf 'Debug: Full API response:\n%s\n' "$pr_response" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6878
+ printf 'Debug: Full API response:\n%s\n' "$pr_response" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6876
6879
  fi
6877
6880
  GITHUB_PR_EXIT=9
6878
6881
  pr_created=0
@@ -6882,17 +6885,17 @@ EOF
6882
6885
  else
6883
6886
  # API returned an error
6884
6887
  if is_github_pr_error_retryable "$pr_http_status" "$GITHUB_API_ERROR_TYPE" && [ "$retry_count" -lt "$((max_retries - 1))" ]; then
6885
- printf 'GitHub API returned retryable error (attempt %d): %s (HTTP %s)\n' $((retry_count + 1)) "$GITHUB_API_ERROR_TYPE" "$pr_http_status" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6888
+ printf 'GitHub API returned retryable error (attempt %d): %s (HTTP %s)\n' $((retry_count + 1)) "$GITHUB_API_ERROR_TYPE" "$pr_http_status" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6886
6889
  retry_count=$((retry_count + 1))
6887
6890
  rm -f "$pr_response_file"
6888
6891
  continue
6889
6892
  else
6890
6893
  # Permanent error, give up
6891
- printf 'Failed to create PR. API error: %s\n' "$GITHUB_API_ERROR_MESSAGE" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
6894
+ printf 'Failed to create PR. API error: %s\n' "$GITHUB_API_ERROR_MESSAGE" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
6892
6895
  emit_error_event "github_pr_api_failed" "GitHub API error ($GITHUB_API_ERROR_TYPE): $GITHUB_API_ERROR_MESSAGE (HTTP $GITHUB_API_HTTP_STATUS)" "exit"
6893
6896
  if [ "${KASEKI_DEBUG:-0}" = "1" ]; then
6894
- printf 'Debug: API error type: %s, HTTP status: %s\n' "$GITHUB_API_ERROR_TYPE" "$GITHUB_API_HTTP_STATUS" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6895
- printf 'Debug: Full response:\n%s\n' "$pr_response" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
6897
+ printf 'Debug: API error type: %s, HTTP status: %s\n' "$GITHUB_API_ERROR_TYPE" "$GITHUB_API_HTTP_STATUS" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6898
+ printf 'Debug: Full response:\n%s\n' "$pr_response" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
6896
6899
  fi
6897
6900
  GITHUB_PR_EXIT=9
6898
6901
  pr_created=0
@@ -6925,7 +6928,7 @@ if [ "$GITHUB_APP_ENABLED" = "1" ]; then
6925
6928
  printf 'ERROR: GitHub operations preflight health check failed\n' >&2
6926
6929
  printf 'GitHub App is enabled but configuration or dependencies are missing.\n' >&2
6927
6930
  printf 'Proceeding with kaseki run, but GitHub operations will be skipped or fail.\n' >&2
6928
- emit_error_event "github_preflight_failed" "GitHub operations health check failed; check ${KASEKI_RESULTS_DIR}/github-health-check.log for details" "continue"
6931
+ emit_error_event "github_preflight_failed" "GitHub operations health check failed; check "${KASEKI_RESULTS_DIR}"/github-health-check.log for details" "continue"
6929
6932
  fi
6930
6933
  fi
6931
6934
 
@@ -6949,7 +6952,7 @@ unset OPENROUTER_API_KEY secret_content
6949
6952
  if [ -z "$openrouter_api_key" ]; then
6950
6953
  set_current_stage "agent setup"
6951
6954
  openrouter_api_key_file="${OPENROUTER_API_KEY_FILE:-/agents/secrets/openrouter_api_key}"
6952
- printf 'Missing OpenRouter API key. Set OPENROUTER_API_KEY or provide %s.\n' "$openrouter_api_key_file" | tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log >&2
6955
+ printf 'Missing OpenRouter API key. Set OPENROUTER_API_KEY or provide %s.\n' "$openrouter_api_key_file" | tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log >&2
6953
6956
  : > "$RAW_EVENTS"
6954
6957
  PI_EXIT=2
6955
6958
  STATUS=2
@@ -6960,7 +6963,7 @@ fi
6960
6963
  if ! run_clone_repository; then
6961
6964
  exit 0
6962
6965
  fi
6963
- cd ${KASEKI_WORKSPACE_DIR}/repo || { STATUS=1; FAILED_COMMAND="enter repository"; exit "$STATUS"; }
6966
+ cd "${KASEKI_WORKSPACE_DIR}"/repo || { STATUS=1; FAILED_COMMAND="enter repository"; exit "$STATUS"; }
6964
6967
 
6965
6968
  prepare_dependencies() {
6966
6969
  if [ ! -f package.json ]; then
@@ -7190,7 +7193,7 @@ if [ "$KASEKI_BASELINE_VALIDATION_ENABLED" = "1" ] && [ "$KASEKI_PRE_AGENT_VALID
7190
7193
  emit_progress "baseline validation cache" "failed to save (non-blocking)"
7191
7194
  fi
7192
7195
  # Cleanup baseline workspace to save space
7193
- rm -rf ${KASEKI_WORKSPACE_BASELINE_DIR} 2>/dev/null || true
7196
+ rm -rf "${KASEKI_WORKSPACE_BASELINE_DIR}" 2>/dev/null || true
7194
7197
  else
7195
7198
  BASELINE_CACHE_STATUS="checkout_failed"
7196
7199
  emit_error_event "baseline_checkout_failed" "Failed to setup baseline for test failure comparison; continuing without baseline" "continue"
@@ -7207,13 +7210,13 @@ if [ "$KASEKI_PRE_AGENT_VALIDATION" = "0" ]; then
7207
7210
  printf '\n==> pre-agent validation\n'
7208
7211
  set_current_stage "pre-agent validation"
7209
7212
  emit_progress "pre-agent validation" "skipped by KASEKI_PRE_AGENT_VALIDATION=0"
7210
- printf 'Pre-agent validation skipped because KASEKI_PRE_AGENT_VALIDATION=0.\n' | tee -a ${KASEKI_RESULTS_DIR}/pre-validation.log
7213
+ printf 'Pre-agent validation skipped because KASEKI_PRE_AGENT_VALIDATION=0.\n' | tee -a "${KASEKI_RESULTS_DIR}"/pre-validation.log
7211
7214
  record_stage_timing "pre-agent validation" 0 0 "skipped_by_config"
7212
7215
  else
7213
7216
  run_validation_commands \
7214
7217
  "pre-agent validation" \
7215
7218
  "$KASEKI_PRE_AGENT_VALIDATION_COMMANDS" \
7216
- ${KASEKI_RESULTS_DIR}/pre-validation.log \
7219
+ "${KASEKI_RESULTS_DIR}"/pre-validation.log \
7217
7220
  "$PRE_VALIDATION_RAW_LOG" \
7218
7221
  "$PRE_VALIDATION_TIMINGS_FILE" \
7219
7222
  "$PRE_VALIDATION_ENV_LOG" \
@@ -7242,7 +7245,7 @@ set_current_stage "typescript precheck"
7242
7245
  if ! run_typescript_precheck; then
7243
7246
  if [ "$KASEKI_SCOUTING" = "1" ]; then
7244
7247
  # If scouting is enabled (experimental path), continue anyway with warning
7245
- printf 'WARNING: TypeScript pre-check failed, but continuing due to scouting mode being enabled.\n' | tee -a ${KASEKI_RESULTS_DIR}/quality.log
7248
+ printf 'WARNING: TypeScript pre-check failed, but continuing due to scouting mode being enabled.\n' | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
7246
7249
  else
7247
7250
  # Without scouting, TypeScript failures are fatal
7248
7251
  STATUS="$TS_PRE_CHECK_EXIT"
@@ -7258,13 +7261,13 @@ printf 'Pi version: %s\n' "$PI_VERSION"
7258
7261
  # === Phase 1: Early Filesystem Diagnostics (Before Scouting) ===
7259
7262
  # Detects read-only filesystem constraints that would cause silent scouting failures
7260
7263
  check_filesystem_capabilities() {
7261
- local results_dir="${KASEKI_RESULTS_DIR:-${KASEKI_RESULTS_DIR}}"
7264
+ local results_dir="$KASEKI_RESULTS_DIR"
7262
7265
  local filesystem_writable=true
7263
7266
  local readonly_reason=""
7264
7267
 
7265
7268
  emit_progress "filesystem capabilities check" "verifying write capabilities for artifacts"
7266
7269
 
7267
- # Test ${KASEKI_RESULTS_DIR}/ writability
7270
+ # Test "${KASEKI_RESULTS_DIR}"/ writability
7268
7271
  if [ ! -w "$results_dir" ]; then
7269
7272
  filesystem_writable=false
7270
7273
  readonly_reason="${KASEKI_RESULTS_DIR} is READ-ONLY (Docker mounted with :ro or container --read-only flag)"
@@ -7277,13 +7280,13 @@ check_filesystem_capabilities() {
7277
7280
  printf ' - Container UID: %d\n' "$(id -u)"
7278
7281
  printf ' - Expected reason: Docker mounted with :ro flag or container --read-only\n'
7279
7282
  printf '\nImpact:\n'
7280
- printf ' - Scouting Pi agent will exit 0 but ${KASEKI_RESULTS_DIR}/scouting-candidate.json will be MISSING\n'
7283
+ printf ' - Scouting Pi agent will exit 0 but "${KASEKI_RESULTS_DIR}"/scouting-candidate.json will be MISSING\n'
7281
7284
  printf ' - Validation logs and artifacts cannot be written\n'
7282
7285
  printf ' - This causes exit code 86 (scouting validation failure)\n'
7283
7286
  printf '\nFix: Remount ${KASEKI_RESULTS_DIR} as read-write\n'
7284
7287
  printf ' docker run -v /path/to${KASEKI_RESULTS_DIR}:${KASEKI_RESULTS_DIR}:rw ...\n'
7285
7288
  printf 'Or remove --read-only flag from Docker run command\n'
7286
- } | tee -a ${KASEKI_RESULTS_DIR}/scouting-stderr.log
7289
+ } | tee -a "${KASEKI_RESULTS_DIR}"/scouting-stderr.log
7287
7290
  else
7288
7291
  # Test actual write capability
7289
7292
  local test_file="$results_dir/.kaseki-fs-test-$$"
@@ -7298,14 +7301,14 @@ check_filesystem_capabilities() {
7298
7301
  fi
7299
7302
 
7300
7303
  # Record in metadata for post-mortem analysis
7301
- printf '%s\n' "$filesystem_writable" > ${KASEKI_RESULTS_DIR}/filesystem-writable-at-start.txt
7302
- [ -n "$readonly_reason" ] && printf '%s\n' "$readonly_reason" > ${KASEKI_RESULTS_DIR}/filesystem-readonly-reason.txt
7304
+ printf '%s\n' "$filesystem_writable" > "${KASEKI_RESULTS_DIR}"/filesystem-writable-at-start.txt
7305
+ [ -n "$readonly_reason" ] && printf '%s\n' "$readonly_reason" > "${KASEKI_RESULTS_DIR}"/filesystem-readonly-reason.txt
7303
7306
 
7304
7307
  if [ "$filesystem_writable" = "false" ]; then
7305
7308
  if [ "$KASEKI_BASELINE_VALIDATION_ENABLED" = "1" ]; then
7306
7309
  emit_progress "baseline validation preparation" "DISABLED due to read-only filesystem detected"
7307
7310
  KASEKI_BASELINE_VALIDATION_ENABLED="0"
7308
- printf '[filesystem-diagnostic] Baseline validation auto-disabled due to read-only filesystem\n' | tee -a ${KASEKI_RESULTS_DIR}/quality.log
7311
+ printf '[filesystem-diagnostic] Baseline validation auto-disabled due to read-only filesystem\n' | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
7309
7312
  fi
7310
7313
  return 1
7311
7314
  fi
@@ -7355,7 +7358,7 @@ if [ "$KASEKI_SCOUTING" = "1" ] && [ -f "$SCOUTING_ARTIFACT" ]; then
7355
7358
  export KASEKI_VALIDATION_ALLOWLIST="$merged_validation_allowlist"
7356
7359
 
7357
7360
  # Log merge decisions with structured JSON construction so pattern text is escaped safely.
7358
- append_jsonl_object ${KASEKI_RESULTS_DIR}/metadata.jsonl \
7361
+ append_jsonl_object "${KASEKI_RESULTS_DIR}"/metadata.jsonl \
7359
7362
  "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
7360
7363
  "event=allowlist_merge" \
7361
7364
  "scouting_agent_patterns=$scouting_agent_patterns" \
@@ -7368,14 +7371,14 @@ if [ "$KASEKI_SCOUTING" = "1" ] && [ -f "$SCOUTING_ARTIFACT" ]; then
7368
7371
  allowlist_merge_status="merged"
7369
7372
 
7370
7373
  # Run coverage validation with dry-run
7371
- if [ -s ${KASEKI_RESULTS_DIR}/changed-files.txt ]; then
7372
- run_scouting_allowlist_coverage "$SCOUTING_ARTIFACT" 2>&1 | tee -a ${KASEKI_RESULTS_DIR}/quality.log
7374
+ if [ -s "${KASEKI_RESULTS_DIR}"/changed-files.txt ]; then
7375
+ run_scouting_allowlist_coverage "$SCOUTING_ARTIFACT" 2>&1 | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
7373
7376
  fi
7374
7377
 
7375
7378
  emit_progress "derive allowlist from scouting" "finished (status=$allowlist_merge_status)"
7376
7379
  else
7377
7380
  # Pattern validation failed - fail fast
7378
- printf 'ERROR: Derived allowlist patterns failed validation. Cannot proceed.\n' | tee -a ${KASEKI_RESULTS_DIR}/quality.log >&2
7381
+ printf 'ERROR: Derived allowlist patterns failed validation. Cannot proceed.\n' | tee -a "${KASEKI_RESULTS_DIR}"/quality.log >&2
7379
7382
  STATUS=86
7380
7383
  FAILED_COMMAND="allowlist pattern validation"
7381
7384
  emit_error_event "scouting_allowlist_invalid" "Derived allowlist patterns failed validation" "exit"
@@ -7383,7 +7386,7 @@ if [ "$KASEKI_SCOUTING" = "1" ] && [ -f "$SCOUTING_ARTIFACT" ]; then
7383
7386
  fi
7384
7387
  else
7385
7388
  # Derivation failed - log and fail fast
7386
- printf 'ERROR: Failed to derive allowlist from scouting artifact: %s\n' "$scouting_output" | tee -a ${KASEKI_RESULTS_DIR}/quality.log >&2
7389
+ printf 'ERROR: Failed to derive allowlist from scouting artifact: %s\n' "$scouting_output" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log >&2
7387
7390
  STATUS=86
7388
7391
  FAILED_COMMAND="allowlist derivation from scouting"
7389
7392
  emit_error_event "scouting_allowlist_derivation_failed" "Failed to derive allowlist from scouting artifact" "exit"
@@ -7421,7 +7424,7 @@ if [ "$KASEKI_DRY_RUN" = "1" ]; then
7421
7424
  printf ' Model: %s\n' "$KASEKI_MODEL"
7422
7425
  printf ' Timeout: %s seconds\n' "$KASEKI_AGENT_TIMEOUT_SECONDS"
7423
7426
  printf ' Task: %s\n' "$TASK_PROMPT"
7424
- } | tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log
7427
+ } | tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log
7425
7428
  emit_progress "pi coding agent" "skipped (dry-run)"
7426
7429
  record_stage_timing "pi coding agent" "0" "$PI_DURATION_SECONDS" "dry_run=true"
7427
7430
  else
@@ -7433,9 +7436,9 @@ else
7433
7436
  OPENROUTER_API_KEY="$openrouter_api_key" \
7434
7437
  timeout --signal=SIGTERM "$KASEKI_AGENT_TIMEOUT_SECONDS" \
7435
7438
  pi --mode json --no-session --provider "$KASEKI_PROVIDER" --model "$KASEKI_MODEL" "$agent_prompt" \
7436
- 2> >(tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log >&2) \
7439
+ 2> >(tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log >&2) \
7437
7440
  | tee "$RAW_EVENTS" \
7438
- | kaseki-pi-progress-stream ${KASEKI_RESULTS_DIR}/progress.jsonl ${KASEKI_RESULTS_DIR}/progress.log
7441
+ | kaseki-pi-progress-stream "${KASEKI_RESULTS_DIR}"/progress.jsonl "${KASEKI_RESULTS_DIR}"/progress.log
7439
7442
  PI_EXIT="${PIPESTATUS[0]}"
7440
7443
  unset agent_prompt
7441
7444
  PI_DURATION_SECONDS=$(($(date +%s) - PI_START_EPOCH))
@@ -7444,7 +7447,7 @@ else
7444
7447
  record_stage_timing "pi coding agent" "$PI_EXIT" "$PI_DURATION_SECONDS" "timeout_seconds=$KASEKI_AGENT_TIMEOUT_SECONDS"
7445
7448
 
7446
7449
  if [ "$KASEKI_DEBUG_RAW_EVENTS" = "1" ]; then
7447
- cp "$RAW_EVENTS" ${KASEKI_RESULTS_DIR}/pi-events.raw.jsonl
7450
+ cp "$RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/pi-events.raw.jsonl
7448
7451
  fi
7449
7452
 
7450
7453
  PI_EXTRACTION_DEPS_OK=1
@@ -7455,7 +7458,7 @@ else
7455
7458
  missing_executables+=("$required_exec")
7456
7459
  fi
7457
7460
  done
7458
- for helper_file in ${KASEKI_APP_LIB_DIR}/event-aggregator.js ${KASEKI_APP_LIB_DIR}/timestamp-tracker.js ${KASEKI_APP_LIB_DIR}/progress-stream-utils.js; do
7461
+ for helper_file in "${KASEKI_APP_LIB_DIR}"/event-aggregator.js ${KASEKI_APP_LIB_DIR}/timestamp-tracker.js ${KASEKI_APP_LIB_DIR}/progress-stream-utils.js; do
7459
7462
  if [ ! -f "$helper_file" ]; then
7460
7463
  missing_helpers+=("$helper_file")
7461
7464
  fi
@@ -7468,34 +7471,34 @@ else
7468
7471
  [ -z "$missing_helpers_joined" ] && missing_helpers_joined="none"
7469
7472
  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")
7470
7473
  printf '%s
7471
- ' "$extraction_error" | tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log ${KASEKI_RESULTS_DIR}/quality.log >&2
7474
+ ' "$extraction_error" | tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log "${KASEKI_RESULTS_DIR}"/quality.log >&2
7472
7475
  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"
7473
7476
  if [ "$STATUS" -eq 0 ]; then
7474
7477
  STATUS=87
7475
7478
  FAILED_COMMAND="pi artifact extraction dependency validation"
7476
7479
  fi
7477
- cp "$RAW_EVENTS" ${KASEKI_RESULTS_DIR}/pi-events.raw.jsonl 2>/dev/null || true
7480
+ cp "$RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/pi-events.raw.jsonl 2>/dev/null || true
7478
7481
  fi
7479
7482
 
7480
7483
  FILTER_EXIT=0
7481
7484
  if [ "$PI_EXTRACTION_DEPS_OK" -eq 1 ]; then
7482
7485
  set +e
7483
- kaseki-pi-event-filter "$RAW_EVENTS" ${KASEKI_RESULTS_DIR}/pi-events.jsonl ${KASEKI_RESULTS_DIR}/pi-summary.json
7486
+ kaseki-pi-event-filter "$RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/pi-events.jsonl "${KASEKI_RESULTS_DIR}"/pi-summary.json
7484
7487
  FILTER_EXIT=$?
7485
7488
  set +e
7486
7489
  fi
7487
7490
  if [ "$FILTER_EXIT" -ne 0 ]; then
7488
- printf 'pi-event-filter failed with exit %s; raw events preserved as fallback artifact\n' "$FILTER_EXIT" | tee -a ${KASEKI_RESULTS_DIR}/quality.log
7489
- printf 'ERROR: kaseki-pi-event-filter failed with exit %s while exporting Pi events\n' "$FILTER_EXIT" | tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log >&2
7491
+ printf 'pi-event-filter failed with exit %s; raw events preserved as fallback artifact\n' "$FILTER_EXIT" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
7492
+ printf 'ERROR: kaseki-pi-event-filter failed with exit %s while exporting Pi events\n' "$FILTER_EXIT" | tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log >&2
7490
7493
  emit_error_event "pi_event_filter_failed" "kaseki-pi-event-filter exited with code $FILTER_EXIT" "continue"
7491
7494
  if [ "$STATUS" -eq 0 ]; then
7492
7495
  STATUS="$FILTER_EXIT"
7493
7496
  FAILED_COMMAND="kaseki-pi-event-filter"
7494
7497
  fi
7495
- cp "$RAW_EVENTS" ${KASEKI_RESULTS_DIR}/pi-events.raw.jsonl 2>/dev/null || true
7498
+ cp "$RAW_EVENTS" "${KASEKI_RESULTS_DIR}"/pi-events.raw.jsonl 2>/dev/null || true
7496
7499
  fi
7497
- if [ -s "$RAW_EVENTS" ] && { [ ! -s ${KASEKI_RESULTS_DIR}/pi-events.jsonl ] || [ ! -s ${KASEKI_RESULTS_DIR}/pi-summary.json ]; }; then
7498
- printf 'ERROR: pi event export incomplete; raw events are non-empty but event artifacts are missing/empty\n' | tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log >&2
7500
+ if [ -s "$RAW_EVENTS" ] && { [ ! -s "${KASEKI_RESULTS_DIR}"/pi-events.jsonl ] || [ ! -s "${KASEKI_RESULTS_DIR}"/pi-summary.json ]; }; then
7501
+ printf 'ERROR: pi event export incomplete; raw events are non-empty but event artifacts are missing/empty\n' | tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log >&2
7499
7502
  emit_error_event "pi_event_export_incomplete" "RAW_EVENTS has data but exported artifacts are empty or missing" "continue"
7500
7503
  if [ "$STATUS" -eq 0 ]; then
7501
7504
  STATUS=86
@@ -7504,16 +7507,16 @@ else
7504
7507
  fi
7505
7508
 
7506
7509
  # Process hashline_edit events (non-fatal phase; failures don't block pipeline)
7507
- if [ "$KASEKI_HASHLINE_EDITS" != "0" ] && [ -s ${KASEKI_RESULTS_DIR}/pi-events.jsonl ]; then
7510
+ if [ "$KASEKI_HASHLINE_EDITS" != "0" ] && [ -s "${KASEKI_RESULTS_DIR}"/pi-events.jsonl ]; then
7508
7511
  emit_progress "hashline validation" "started"
7509
7512
  HASHLINE_EXIT=0
7510
7513
  set +e
7511
- npx tsx ${KASEKI_APP_LIB_DIR}/hashline-event-handler-cli.js ${KASEKI_RESULTS_DIR}/pi-events.jsonl /workspace ${KASEKI_RESULTS_DIR}/hashline-events.jsonl ${KASEKI_RESULTS_DIR}/hashline-summary.json 2>> ${KASEKI_RESULTS_DIR}/hashline-validation.log
7514
+ npx tsx "${KASEKI_APP_LIB_DIR}"/hashline-event-handler-cli.js "${KASEKI_RESULTS_DIR}"/pi-events.jsonl /workspace ${KASEKI_RESULTS_DIR}/hashline-events.jsonl ${KASEKI_RESULTS_DIR}/hashline-summary.json 2>> "${KASEKI_RESULTS_DIR}"/hashline-validation.log
7512
7515
  HASHLINE_EXIT=$?
7513
7516
  set +e
7514
7517
 
7515
7518
  if [ "$HASHLINE_EXIT" -ne 0 ]; then
7516
- printf 'Warning: hashline validation exited with code %s (non-fatal; continuing pipeline)\n' "$HASHLINE_EXIT" | tee -a ${KASEKI_RESULTS_DIR}/hashline-validation.log
7519
+ printf 'Warning: hashline validation exited with code %s (non-fatal; continuing pipeline)\n' "$HASHLINE_EXIT" | tee -a "${KASEKI_RESULTS_DIR}"/hashline-validation.log
7517
7520
  emit_event "warning" "warning_type=hashline_validation_failed" "detail=hashline_edit processing exited with code $HASHLINE_EXIT"
7518
7521
  else
7519
7522
  emit_progress "hashline validation" "completed"
@@ -7570,7 +7573,7 @@ fi
7570
7573
 
7571
7574
  if [ "$KASEKI_DRY_RUN" != "1" ]; then
7572
7575
  if [ "$PI_EXIT" -eq 124 ]; then
7573
- printf 'pi timeout after %ss (exit 124)\n' "$KASEKI_AGENT_TIMEOUT_SECONDS" | tee -a ${KASEKI_RESULTS_DIR}/pi-stderr.log >&2
7576
+ printf 'pi timeout after %ss (exit 124)\n' "$KASEKI_AGENT_TIMEOUT_SECONDS" | tee -a "${KASEKI_RESULTS_DIR}"/pi-stderr.log >&2
7574
7577
  if [ "$STATUS" -eq 0 ]; then
7575
7578
  STATUS=124
7576
7579
  FAILED_COMMAND="pi coding agent timeout"
@@ -7609,11 +7612,11 @@ printf '\n==> quality checks\n'
7609
7612
  set_current_stage "quality checks"
7610
7613
  emit_progress "quality checks" "started"
7611
7614
  stage_start="$(date +%s)"
7612
- diff_size="$(wc -c < ${KASEKI_RESULTS_DIR}/git.diff | tr -d ' ')"
7615
+ diff_size="$(wc -c < "${KASEKI_RESULTS_DIR}"/git.diff | tr -d ' ')"
7613
7616
  if [ "$diff_size" -gt "$KASEKI_MAX_DIFF_BYTES" ]; then
7614
7617
  QUALITY_EXIT=4
7615
7618
  QUALITY_FAILURE_REASON="max_diff_bytes: $diff_size bytes exceeds limit of $KASEKI_MAX_DIFF_BYTES bytes"
7616
- printf 'git.diff is too large: %s bytes > %s bytes\n' "$diff_size" "$KASEKI_MAX_DIFF_BYTES" | tee -a ${KASEKI_RESULTS_DIR}/quality.log
7619
+ printf 'git.diff is too large: %s bytes > %s bytes\n' "$diff_size" "$KASEKI_MAX_DIFF_BYTES" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
7617
7620
  emit_event "quality_gate_rule_evaluated" "rule=max_diff_bytes" "passed=false" "actual=$diff_size" "limit=$KASEKI_MAX_DIFF_BYTES"
7618
7621
  else
7619
7622
  emit_event "quality_gate_rule_evaluated" "rule=max_diff_bytes" "passed=true" "actual=$diff_size" "limit=$KASEKI_MAX_DIFF_BYTES"
@@ -7628,17 +7631,17 @@ if [ -n "$allowlist_regex" ]; then
7628
7631
  if ! printf '%s\n' "$changed_file" | grep -Eq "^(${allowlist_regex})$"; then
7629
7632
  QUALITY_EXIT=5
7630
7633
  QUALITY_FAILURE_REASON="allowlist_check: file '$changed_file' not in allowlist"
7631
- printf 'changed file outside allowlist: %s\n' "$changed_file" | tee -a ${KASEKI_RESULTS_DIR}/quality.log
7634
+ printf 'changed file outside allowlist: %s\n' "$changed_file" | tee -a "${KASEKI_RESULTS_DIR}"/quality.log
7632
7635
  emit_event "quality_gate_rule_evaluated" "rule=allowlist_check" "passed=false" "file=$changed_file"
7633
7636
  else
7634
7637
  emit_event "quality_gate_rule_evaluated" "rule=allowlist_check" "passed=true" "file=$changed_file"
7635
7638
  fi
7636
- done < ${KASEKI_RESULTS_DIR}/changed-files.txt
7639
+ done < "${KASEKI_RESULTS_DIR}"/changed-files.txt
7637
7640
  fi
7638
7641
 
7639
7642
  if [ -f package.json ] && node -e "const p=require('./package.json'); process.exit(p.scripts && p.scripts['format:check'] ? 0 : 1)" 2>/dev/null; then
7640
7643
  format_command="npm run format:check"
7641
- printf '%s\n' "$format_command" >> ${KASEKI_RESULTS_DIR}/format-check-command.txt
7644
+ printf '%s\n' "$format_command" >> "${KASEKI_RESULTS_DIR}"/format-check-command.txt
7642
7645
  fi
7643
7646
  record_stage_timing "quality checks" "$QUALITY_EXIT" "$(($(date +%s) - stage_start))" "diff_size_bytes=$diff_size"
7644
7647
 
@@ -7647,7 +7650,7 @@ run_expectation_mismatch_detector
7647
7650
 
7648
7651
  pre_validation_goal_check_diff_hash=""
7649
7652
  if [ "$STATUS" -eq 0 ] && [ "$PI_EXIT" -eq 0 ] && [ "$QUALITY_EXIT" -eq 0 ]; then
7650
- pre_validation_goal_check_diff_hash="$(sha256sum ${KASEKI_RESULTS_DIR}/git.diff 2>/dev/null | awk '{print $1}')"
7653
+ pre_validation_goal_check_diff_hash="$(sha256sum "${KASEKI_RESULTS_DIR}"/git.diff 2>/dev/null | awk '{print $1}')"
7651
7654
  run_goal_check "$coding_attempt"
7652
7655
  collect_goal_check_feedback "$INSTANCE_NAME"
7653
7656
  snapshot_attempt_artifacts "$coding_attempt"
@@ -7679,18 +7682,18 @@ log_validation_environment() {
7679
7682
  printf '[validation environment] PATH=%s\n' "$PATH"
7680
7683
  printf '[validation environment] NODE_OPTIONS=%s\n' "${NODE_OPTIONS:-<not set>}"
7681
7684
  printf '[validation environment] NODE_PATH=%s\n' "${NODE_PATH:-<not set>}"
7682
- printf '[validation environment] disk_space_available=%s\n' "$(df -h ${KASEKI_RESULTS_DIR} 2>/dev/null | tail -1 | awk '{print $4}' || echo '<df failed>')"
7683
- printf '[validation environment] disk_space_used=%s\n' "$(du -sh ${KASEKI_RESULTS_DIR} 2>/dev/null | cut -f1 || echo '<du failed>')"
7684
- } | tee -a ${KASEKI_RESULTS_DIR}/validation.log "$VALIDATION_ENV_LOG"
7685
+ printf '[validation environment] disk_space_available=%s\n' "$(df -h "${KASEKI_RESULTS_DIR}" 2>/dev/null | tail -1 | awk '{print $4}' || echo '<df failed>')"
7686
+ printf '[validation environment] disk_space_used=%s\n' "$(du -sh "${KASEKI_RESULTS_DIR}" 2>/dev/null | cut -f1 || echo '<du failed>')"
7687
+ } | tee -a "${KASEKI_RESULTS_DIR}"/validation.log "$VALIDATION_ENV_LOG"
7685
7688
  }
7686
7689
  log_validation_environment
7687
- collect_changed_file_state ${KASEKI_RESULTS_DIR}/validation-before-state.txt
7690
+ collect_changed_file_state "${KASEKI_RESULTS_DIR}"/validation-before-state.txt
7688
7691
 
7689
7692
  if [ "$KASEKI_DRY_RUN" = "1" ] || [ -z "$KASEKI_VALIDATION_COMMANDS" ] || [ "$KASEKI_VALIDATION_COMMANDS" = "none" ]; then
7690
7693
  run_validation_commands \
7691
7694
  "validation" \
7692
7695
  "$KASEKI_VALIDATION_COMMANDS" \
7693
- ${KASEKI_RESULTS_DIR}/validation.log \
7696
+ "${KASEKI_RESULTS_DIR}"/validation.log \
7694
7697
  "$VALIDATION_RAW_LOG" \
7695
7698
  "$VALIDATION_TIMINGS_FILE" \
7696
7699
  "$VALIDATION_ENV_LOG" \
@@ -7699,7 +7702,7 @@ elif [ "$QUALITY_EXIT" -ne 0 ]; then
7699
7702
  printf '\n==> validation\n'
7700
7703
  set_current_stage "validation"
7701
7704
  emit_progress "validation" "started"
7702
- printf 'Validation skipped because quality gates failed with exit %s.\n' "$QUALITY_EXIT" | tee -a ${KASEKI_RESULTS_DIR}/validation.log
7705
+ printf 'Validation skipped because quality gates failed with exit %s.\n' "$QUALITY_EXIT" | tee -a "${KASEKI_RESULTS_DIR}"/validation.log
7703
7706
  VALIDATION_EXIT="$QUALITY_EXIT"
7704
7707
  if [ -z "$VALIDATION_FAILURE_REASON" ]; then
7705
7708
  VALIDATION_FAILURE_REASON="quality_gate_failed: $QUALITY_FAILURE_REASON"
@@ -7710,14 +7713,14 @@ elif [ "$PI_EXIT" -ne 0 ] && [ "$KASEKI_VALIDATE_AFTER_AGENT_FAILURE" != "1" ];
7710
7713
  printf '\n==> validation\n'
7711
7714
  set_current_stage "validation"
7712
7715
  emit_progress "validation" "started"
7713
- 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 ${KASEKI_RESULTS_DIR}/validation.log
7716
+ 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 "${KASEKI_RESULTS_DIR}"/validation.log
7714
7717
  record_stage_timing "validation" "$PI_EXIT" 0 "skipped_after_agent_failure"
7715
7718
  emit_progress "validation" "finished with exit $VALIDATION_EXIT"
7716
7719
  else
7717
7720
  run_validation_commands \
7718
7721
  "validation" \
7719
7722
  "$KASEKI_VALIDATION_COMMANDS" \
7720
- ${KASEKI_RESULTS_DIR}/validation.log \
7723
+ "${KASEKI_RESULTS_DIR}"/validation.log \
7721
7724
  "$VALIDATION_RAW_LOG" \
7722
7725
  "$VALIDATION_TIMINGS_FILE" \
7723
7726
  "$VALIDATION_ENV_LOG" \
@@ -7731,18 +7734,18 @@ fi
7731
7734
 
7732
7735
  # Check validation-phase allowlist (if configured)
7733
7736
  if [ "$VALIDATION_EXIT" -eq 0 ]; then
7734
- collect_changed_file_state ${KASEKI_RESULTS_DIR}/validation-after-state.txt
7737
+ collect_changed_file_state "${KASEKI_RESULTS_DIR}"/validation-after-state.txt
7735
7738
  collect_git_artifacts
7736
7739
  if ! check_validation_allowlist; then
7737
7740
  : # Exit code already set in check_validation_allowlist
7738
7741
  fi
7739
7742
  fi
7740
7743
 
7741
- post_validation_goal_check_diff_hash="$(sha256sum ${KASEKI_RESULTS_DIR}/git.diff 2>/dev/null | awk '{print $1}')"
7744
+ post_validation_goal_check_diff_hash="$(sha256sum "${KASEKI_RESULTS_DIR}"/git.diff 2>/dev/null | awk '{print $1}')"
7742
7745
  if [ "$STATUS" -eq 0 ] && [ "$PI_EXIT" -eq 0 ] && [ "$QUALITY_EXIT" -eq 0 ] && [ "$VALIDATION_EXIT" -eq 0 ] && \
7743
7746
  [ -n "$pre_validation_goal_check_diff_hash" ] && [ -n "$post_validation_goal_check_diff_hash" ] && \
7744
7747
  [ "$post_validation_goal_check_diff_hash" != "$pre_validation_goal_check_diff_hash" ]; then
7745
- printf 'Validation commands changed the final git diff; re-running goal check against post-validation artifacts.\n' | tee -a ${KASEKI_RESULTS_DIR}/goal-check-stderr.log
7748
+ printf 'Validation commands changed the final git diff; re-running goal check against post-validation artifacts.\n' | tee -a "${KASEKI_RESULTS_DIR}"/goal-check-stderr.log
7746
7749
  emit_progress "goal check" "re-running after validation changed the final diff (attempt $coding_attempt)"
7747
7750
  run_goal_check "$coding_attempt"
7748
7751
  collect_goal_check_feedback "$INSTANCE_NAME"
@@ -7776,14 +7779,14 @@ printf '\n==> secret scan\n'
7776
7779
  set_current_stage "secret scan"
7777
7780
  emit_progress "secret scan" "started"
7778
7781
  stage_start="$(date +%s)"
7779
- : > ${KASEKI_RESULTS_DIR}/secret-scan.log
7782
+ : > "${KASEKI_RESULTS_DIR}"/secret-scan.log
7780
7783
  if [ "$KASEKI_DRY_RUN" = "1" ]; then
7781
- printf '🔄 DRY-RUN MODE: Skipping secret scan (no artifacts to scan)\n' | tee -a ${KASEKI_RESULTS_DIR}/secret-scan.log
7784
+ printf '🔄 DRY-RUN MODE: Skipping secret scan (no artifacts to scan)\n' | tee -a "${KASEKI_RESULTS_DIR}"/secret-scan.log
7782
7785
  SECRET_SCAN_EXIT=0
7783
7786
  record_stage_timing "secret scan" "0" "$(($(date +%s) - stage_start))" "dry_run=true"
7784
7787
  else
7785
7788
  # Run the initial scan
7786
- if grep -R -n -E 'sk-or-[A-Za-z0-9_-]{20,}' ${KASEKI_RESULTS_DIR} ${KASEKI_WORKSPACE_DIR}/repo/.git ${KASEKI_WORKSPACE_DIR}/repo/src ${KASEKI_WORKSPACE_DIR}/repo/tests 2>/dev/null | grep -v '/secret-scan.log:' > ${KASEKI_RESULTS_DIR}/secret-scan.log; then
7789
+ if grep -R -n -E 'sk-or-[A-Za-z0-9_-]{20,}' "${KASEKI_RESULTS_DIR}" "${KASEKI_WORKSPACE_DIR}"/repo/.git "${KASEKI_WORKSPACE_DIR}"/repo/src "${KASEKI_WORKSPACE_DIR}"/repo/tests 2>/dev/null | grep -v '/secret-scan.log:' > "${KASEKI_RESULTS_DIR}"/secret-scan.log; then
7787
7790
  # Matches found - check against allowlist
7788
7791
  if check_secret_scan_allowlist; then
7789
7792
  # All matches are allowlisted
@@ -7801,7 +7804,6 @@ fi
7801
7804
  emit_progress "secret scan" "finished with exit $SECRET_SCAN_EXIT"
7802
7805
 
7803
7806
  run_run_evaluation
7804
- collect_run_evaluation_feedback "$INSTANCE_NAME"
7805
7807
 
7806
7808
  build_github_skip_reasons() {
7807
7809
  GITHUB_SKIP_REASONS=()
@@ -7836,7 +7838,7 @@ printf '\n==> github operations\n'
7836
7838
  set_current_stage "github operations"
7837
7839
  emit_progress "github operations" "started"
7838
7840
  stage_start="$(date +%s)"
7839
- : > ${KASEKI_RESULTS_DIR}/git-push.log
7841
+ : > "${KASEKI_RESULTS_DIR}"/git-push.log
7840
7842
  build_github_skip_reasons
7841
7843
  if [ "${#GITHUB_SKIP_REASONS[@]}" -eq 0 ]; then
7842
7844
  github_app_id_file="$(resolve_github_secret_file "GITHUB_APP_ID_FILE" "github_app_id")"
@@ -7855,7 +7857,7 @@ if [ "${#GITHUB_SKIP_REASONS[@]}" -eq 0 ]; then
7855
7857
  else
7856
7858
  GITHUB_SKIP_REASONS+=("github_app_secrets_missing")
7857
7859
  GITHUB_OPERATION_PHASE="secrets"
7858
- printf -- 'GitHub operations: skipped (reasons: %s)\n' "$(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log >&2
7860
+ printf -- 'GitHub operations: skipped (reasons: %s)\n' "$(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log >&2
7859
7861
  emit_progress "github operations" "skipped: $(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")"
7860
7862
  GITHUB_PUSH_EXIT=7
7861
7863
  fi
@@ -7867,7 +7869,7 @@ else
7867
7869
  "$([ "$QUALITY_EXIT" -eq 0 ] && printf 'passed' || printf 'failed')" \
7868
7870
  "$([ "$SECRET_SCAN_EXIT" -eq 0 ] && printf 'passed' || printf 'failed')" \
7869
7871
  "$DIFF_NONEMPTY" \
7870
- "$GITHUB_APP_ENABLED" | tee -a ${KASEKI_RESULTS_DIR}/git-push.log
7872
+ "$GITHUB_APP_ENABLED" | tee -a "${KASEKI_RESULTS_DIR}"/git-push.log
7871
7873
  emit_progress "github operations" "skipped: $(IFS=,; printf '%s' "${GITHUB_SKIP_REASONS[*]}")"
7872
7874
  fi
7873
7875
  if [ "$GITHUB_APP_ENABLED" = "1" ]; then