File size: 6,851 Bytes
80e070e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | #!/usr/bin/env python3
"""
Stop-hook context updater for RockGlacierNet.
Runs as a plain shell command β no Claude Code permission system involved.
Appends a timestamped session-end entry to agent.md and updates
project_state.md with current git + training state.
Called by .claude/settings.json Stop hook.
"""
import os
import sys
import subprocess
import glob
from datetime import datetime, timezone
ROOT = "/data1/nasiruddink/rockglacier/RockGlacierNet"
AGENT = os.path.join(ROOT, "agent.md")
MEMORY = "/home/nasiruddink/.claude/projects/-data1-nasiruddink-rockglacier-RockGlacierNet/memory"
STATE = os.path.join(MEMORY, "project_state.md")
MEMIDX = os.path.join(MEMORY, "MEMORY.md")
def run(cmd):
try:
return subprocess.check_output(cmd, shell=True, cwd=ROOT,
stderr=subprocess.DEVNULL,
text=True).strip()
except Exception:
return ""
def git_state():
log = run("git log --oneline -5")
status = run("git status --short")
return log, status
def training_state():
csv = os.path.join(ROOT, "outputs/checkpoints/training_history.csv")
ckpt = os.path.join(ROOT, "outputs/checkpoints/best_model.weights.h5")
has_ckpt = os.path.exists(ckpt)
if not os.path.exists(csv):
return None, has_ckpt
try:
# Read last row without pandas dependency
with open(csv) as f:
lines = [l.strip() for l in f if l.strip()]
if len(lines) < 2:
return None, has_ckpt
header = lines[0].split(",")
last = lines[-1].split(",")
row = dict(zip(header, last))
return row, has_ckpt
except Exception:
return None, has_ckpt
def patch_counts():
counts = {}
for split in ("train", "val", "test"):
d = os.path.join(ROOT, "patches", split, "features")
if os.path.isdir(d):
counts[split] = len(glob.glob(os.path.join(d, "*.npy")))
else:
counts[split] = 0
return counts
def append_agent_md(now_str, log, status, row, has_ckpt, patches):
lines = [
"",
"---",
"",
f"## Session β {now_str}",
"",
"*(Auto-recorded by Stop hook β run `/update-context` for full intelligent summary)*",
"",
]
lines += ["**Git state:**"]
if log:
for l in log.splitlines():
lines.append(f"- {l}")
else:
lines.append("- (no commits)")
if status:
lines.append(f"- Modified: {', '.join(status.splitlines()[:5])}")
lines.append("")
lines += ["**Training state:**"]
lines.append(f"- Checkpoint: {'exists' if has_ckpt else 'NOT FOUND'} (best_model.weights.h5)")
if row:
epoch = row.get("epoch", "?")
iou = row.get("val_iou_m", row.get("iou_m", "?"))
dice = row.get("val_dice_coef", "?")
loss = row.get("val_loss", row.get("loss", "?"))
prec = row.get("val_precision_m", "?")
rec = row.get("val_recall_m", "?")
lines.append(f"- Last epoch: {epoch} | val_iou={iou} val_dice={dice} val_loss={loss}")
lines.append(f"- val_precision={prec} val_recall={rec}")
else:
lines.append("- No training history found")
lines.append("")
if any(patches.values()):
lines += ["**Patches:**"]
for sp, n in patches.items():
lines.append(f"- {sp}: {n} patches")
lines.append("")
lines.append("**Next steps:** *(fill in manually or run /update-context)*")
lines.append("")
entry = "\n".join(lines)
os.makedirs(os.path.dirname(AGENT) if os.path.dirname(AGENT) else ".", exist_ok=True)
with open(AGENT, "a") as f:
f.write(entry)
def update_memory(now_str, row, has_ckpt, log):
os.makedirs(MEMORY, exist_ok=True)
# --- MEMORY.md index ---
mem_content = f"""# RockGlacierNet Memory Index
**Last Updated**: {now_str}
- [project_state.md](project_state.md) β Full pipeline, model architecture, dataset stats, training metrics
"""
with open(MEMIDX, "w") as f:
f.write(mem_content)
# --- project_state.md ---
iou = dice = loss = epoch = "unknown"
if row:
epoch = row.get("epoch", "?")
iou = row.get("val_iou_m", row.get("iou_m", "?"))
dice = row.get("val_dice_coef", "?")
loss = row.get("val_loss", row.get("loss", "?"))
last_commit = log.splitlines()[0] if log else "unknown"
state_content = f"""---
name: project-state
description: Full pipeline, model architecture, dataset stats, and current training results for RockGlacierNet
metadata:
type: project
lastUpdated: "{now_str}"
---
## What this project does
Binary semantic segmentation of rock glaciers in Sikkim Himalayas.
Input: 12-band GeoTIFFs (Sentinel-2 + DEM). Output: binary glacier/background mask.
## Current Training State (auto-updated {now_str})
- Last commit: {last_commit}
- Checkpoint: {'EXISTS' if has_ckpt else 'MISSING'} β outputs/checkpoints/best_model.weights.h5
- Last epoch: {epoch}
- val_iou_m: {iou} | val_dice_coef: {dice} | val_loss: {loss}
## Baseline (old 128Γ128 model, 70 epochs)
- val_iou=0.5825, val_dice=0.7618, precision=0.6233, recall=0.8990, f1=0.7362
## Current Result (new 64Γ64 model, 58 epochs β REGRESSION)
- val_iou=0.3308 β IoU dropped 44% vs baseline
- Root cause: 64px patches too small for spatial context (glaciers are 40β60px wide)
- Fix needed: increase patch_size to 128 or use full-image variable-size training
## Active Architecture
- Model: ResidualSwinUNET_64x64 (models/swin_unet.py)
- Input: (64, 64, 12) β Output: (64, 64, 1) sigmoid
- Encoder: 3 pool layers, filters 32β64β128β256
- Bottleneck: 8Γ8Γ256 + 2Γ Swin transformer blocks (64 tokens)
- Loss: Focal(Ξ±=0.75,Ξ³=2.0)Γ2 + Dice | Optimizer: Adam lr=1e-4
## Pipeline Run Order
1. python preprocess/extract_patches.py
2. python preprocess/validate_alignment.py
3. python train.py
4. python evaluate.py
## Key Files
- preprocess/patch_config.py β patch_size=64, stride=32, bg_keep=0.25
- data_generator.py β loads .npy patches, z-score normalizes
- outputs/checkpoints/training_history.csv β per-epoch metrics
- agent.md β full session log and project index
"""
with open(STATE, "w") as f:
f.write(state_content)
def main():
now_str = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
log, status = git_state()
row, has_ckpt = training_state()
patches = patch_counts()
append_agent_md(now_str, log, status, row, has_ckpt, patches)
update_memory(now_str, row, has_ckpt, log)
print(f"Context updated β agent.md entry appended ({now_str}).")
if __name__ == "__main__":
main()
|