attest-plan.sh 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/bin/sh
  2. # planning-with-files: lock the current task_plan.md content with a SHA-256 attestation.
  3. #
  4. # Use after you finalise (or intentionally edit) a plan. The hooks then refuse
  5. # to inject plan content into the model context if the file diverges from the
  6. # attested hash, surfacing a "[PLAN TAMPERED]" warning instead.
  7. #
  8. # Resolution:
  9. # 1. $PLAN_ID env var → ./.planning/$PLAN_ID/
  10. # 2. ./.planning/.active_plan
  11. # 3. Newest ./.planning/<dir>/ by mtime
  12. # 4. Legacy ./task_plan.md at project root
  13. #
  14. # Usage:
  15. # sh scripts/attest-plan.sh # attest the active plan
  16. # sh scripts/attest-plan.sh --show # print the stored hash
  17. # sh scripts/attest-plan.sh --clear # remove the attestation (re-open the plan)
  18. set -u
  19. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  20. RESOLVER="${SCRIPT_DIR}/resolve-plan-dir.sh"
  21. resolve_plan_file() {
  22. plan_dir=""
  23. if [ -f "${RESOLVER}" ]; then
  24. plan_dir="$(sh "${RESOLVER}" 2>/dev/null)"
  25. fi
  26. if [ -n "${plan_dir}" ] && [ -f "${plan_dir}/task_plan.md" ]; then
  27. printf "%s\n" "${plan_dir}/task_plan.md"
  28. return 0
  29. fi
  30. if [ -f "./task_plan.md" ]; then
  31. printf "%s\n" "./task_plan.md"
  32. return 0
  33. fi
  34. return 1
  35. }
  36. attestation_path_for() {
  37. plan_file="$1"
  38. plan_dir="$(dirname "${plan_file}")"
  39. if [ "${plan_dir}" = "." ]; then
  40. # Legacy mode: store at project root.
  41. printf "%s\n" "./.plan-attestation"
  42. else
  43. printf "%s\n" "${plan_dir}/.attestation"
  44. fi
  45. }
  46. compute_hash() {
  47. target="$1"
  48. if command -v sha256sum >/dev/null 2>&1; then
  49. sha256sum "${target}" | awk '{print $1}'
  50. elif command -v shasum >/dev/null 2>&1; then
  51. shasum -a 256 "${target}" | awk '{print $1}'
  52. else
  53. printf "ERROR: no sha256 utility available\n" >&2
  54. return 1
  55. fi
  56. }
  57. mode="attest"
  58. case "${1:-}" in
  59. --show) mode="show" ;;
  60. --clear) mode="clear" ;;
  61. "") mode="attest" ;;
  62. *)
  63. printf "Usage: %s [--show|--clear]\n" "$0" >&2
  64. exit 2
  65. ;;
  66. esac
  67. plan_file="$(resolve_plan_file)" || {
  68. printf "[plan-attest] No task_plan.md found. Create a plan first.\n" >&2
  69. exit 1
  70. }
  71. attestation_file="$(attestation_path_for "${plan_file}")"
  72. case "${mode}" in
  73. show)
  74. if [ -f "${attestation_file}" ]; then
  75. printf "Plan: %s\n" "${plan_file}"
  76. printf "Attestation: %s\n" "${attestation_file}"
  77. printf "SHA-256: %s\n" "$(cat "${attestation_file}")"
  78. else
  79. printf "[plan-attest] No attestation set for %s.\n" "${plan_file}"
  80. exit 1
  81. fi
  82. ;;
  83. clear)
  84. if [ -f "${attestation_file}" ]; then
  85. rm -f "${attestation_file}"
  86. printf "[plan-attest] Cleared attestation for %s.\n" "${plan_file}"
  87. else
  88. printf "[plan-attest] No attestation to clear.\n"
  89. fi
  90. ;;
  91. attest)
  92. hash_val="$(compute_hash "${plan_file}")" || exit 1
  93. printf "%s\n" "${hash_val}" > "${attestation_file}"
  94. short_hash="$(printf "%s" "${hash_val}" | cut -c1-12)"
  95. printf "[plan-attest] Locked %s\n" "${plan_file}"
  96. printf "[plan-attest] SHA-256: %s... (stored in %s)\n" "${short_hash}" "${attestation_file}"
  97. printf "[plan-attest] Hooks will block injection if the file is modified without re-running this command.\n"
  98. ;;
  99. esac
  100. exit 0