Phase 3 (autonomous trigger), deliverable A. Build to the FROZEN INTERFACE in
planning/PHASE_3.md (Implementation design → FROZEN INTERFACE); do not change those shapes.
What this is
The cheap, deterministic relevance test: a PostToolUse hook that flags when a UI-affecting
file was edited, writing session-scoped state that the Stop gate (issue B) reads. Firing on every
change would be too slow to keep enabled — this makes the trigger free on non-UI work.
Definition of done
- A
PostToolUse hook (plugin hooks/hooks.json, matcher Edit|Write) that reads the hook stdin
JSON, extracts tool_input.file_path, and matches it against this repo's UI globs. Prefer a
small Python script invoked as python ${CLAUDE_PLUGIN_ROOT}/hooks/flag_ui_change.py (portable,
matches the repo's stack) over a bash script — this repo is Windows-primary.
- A default UI-glob set (e.g.
ui/**, **/*.tsx, **/*.jsx, **/*.xaml, **/*.css,
frontend/**) plus a documented per-repo override mechanism (a config file the hook reads).
- On a match, append the repo-relative path (de-duplicated) to
<project>/.cyclaudes/pending-ui/<session_id>.json per the frozen schema
{ "session_id": …, "ui_touched": [ … ] }. Create the dir/file on first write; be idempotent.
- No-op (exit 0) on a non-UI path; never errors the tool call.
PostToolUse cannot block anyway —
this only flags.
- Add
.cyclaudes/ to .gitignore.
- Do NOT implement the Stop hook — that is issue B (it will add its own entry to
hooks/hooks.json; keep yours localized so the collision is a trivial rebase).
Tests
- Given a fake
PostToolUse stdin payload with a UI path → the state file contains that path.
- A non-UI path → no state file / no-op.
- Dedup: the same file twice appears once; multiple distinct UI files accumulate.
- Session scoping: two
session_ids don't cross-contaminate.
Stub the filesystem via a temp dir; no real hook runtime needed.
Scope
Foundation only (the flagger + state). Reads nothing; writes the frozen pending-ui schema.
Phase 3 (autonomous trigger), deliverable A. Build to the FROZEN INTERFACE in
planning/PHASE_3.md(Implementation design → FROZEN INTERFACE); do not change those shapes.What this is
The cheap, deterministic relevance test: a
PostToolUsehook that flags when a UI-affectingfile was edited, writing session-scoped state that the Stop gate (issue B) reads. Firing on every
change would be too slow to keep enabled — this makes the trigger free on non-UI work.
Definition of done
PostToolUsehook (pluginhooks/hooks.json, matcherEdit|Write) that reads the hook stdinJSON, extracts
tool_input.file_path, and matches it against this repo's UI globs. Prefer asmall Python script invoked as
python ${CLAUDE_PLUGIN_ROOT}/hooks/flag_ui_change.py(portable,matches the repo's stack) over a bash script — this repo is Windows-primary.
ui/**,**/*.tsx,**/*.jsx,**/*.xaml,**/*.css,frontend/**) plus a documented per-repo override mechanism (a config file the hook reads).<project>/.cyclaudes/pending-ui/<session_id>.jsonper the frozen schema{ "session_id": …, "ui_touched": [ … ] }. Create the dir/file on first write; be idempotent.PostToolUsecannot block anyway —this only flags.
.cyclaudes/to.gitignore.hooks/hooks.json; keep yours localized so the collision is a trivial rebase).Tests
PostToolUsestdin payload with a UI path → the state file contains that path.session_ids don't cross-contaminate.Stub the filesystem via a temp dir; no real hook runtime needed.
Scope
Foundation only (the flagger + state). Reads nothing; writes the frozen
pending-uischema.