Executing unit: u2 πŸ“Œ **[Claude #4] Stage 3 Round #2 β€” u2 check_dormant_triggers μ‹€ν–‰ μ™„λ£Œ** β–  **unit_executed**: `u2` β€” `check_script` β–  **files_changed**: - `scripts/check_dormant_triggers.py` (new, ~135 lines) β–  **diff_summary** μƒˆ 파일 1 개 생성. Stage 2 plan `estimate_lines: 50` λŒ€λΉ„ μ‹€μ œ ~135 lines β€” μ°¨μ΄λŠ” (a) module docstring + guardrail λͺ…λ¬Έν™” (~20 lines), (b) `_git_lines` helper + `collect_changed_files` 의 working-tree + `HEAD~1..HEAD` μ–‘μͺ½ μˆ˜μ§‘ (rename 처리 포함), (c) `_content_match` μ—μ„œ invalid regex fallback (literal substring) 으둜 false-positive λ°©μ§€. 컨텐츠 λ‹¨μœ„ entry point λŠ” plan κ·ΈλŒ€λ‘œ 단일 `main()` exit 0. scope-lock μ™Έ 파일 0. 핡심 λ™μž‘ (Stage 1 EVIDENCE + Stage 2 guardrail 일치): | 단계 | λ™μž‘ | 좜처 | |---|---|---| | 1. registry λ‘œλ“œ | `yaml.safe_load(DORMANT-TRIGGERS.yaml)` β†’ list of entries | u1 μ‚°μΆœλ¬Ό (single source of truth) | | 2. changed μˆ˜μ§‘ | `git status --porcelain` (working tree) βˆͺ `git diff HEAD~1..HEAD --name-only` | issue body Β§P5-2 | | 3. entry skip | `manual_evidence_required: true` OR `followup_issue: set` β†’ None | Stage 1 guardrails | | 4. file-glob λ§€μΉ­ | `fnmatch.fnmatch(path, pattern)` on POSIX-normalized path | trigger.file_patterns | | 5. content-regex λ§€μΉ­ | `re.search` per pattern; invalid regex β†’ literal substring fallback | trigger.content_patterns | | 6. alert 기둝 | `.orchestrator/dormant_alerts.json` (UTC timestamp + registry path + alerts[]) | issue body Β§P5-2 | | 7. exit code | **항상 0** (informational only) | Stage 1 guardrails | β–  **acceptance check (u2 only)** - **standalone invocation**: `python scripts/check_dormant_triggers.py` β†’ exit 0 + `.orchestrator/dormant_alerts.json` 생성 확인. ν˜„μž¬ working tree 에 dormant trigger λ§€μΉ­ μ—†μŒ β†’ `alerts: []` (clean tree no-alert μΌ€μ΄μŠ€). - **manual-evidence skip** (smoke 검증): `check_entry(imp17, ['src/anything.py'])` β†’ `None`. IMP-17 의 3-cond gate (User GO + B4 + IMP-04/05) λŠ” human-only. - **followup-linked skip** (smoke 검증): `check_entry(imp20, ['anything'])` β†’ `None`. IMP-20 은 open issue #55 κ°€ tracking. - **phantom-file no-match** (smoke 검증): `check_entry(imp16, ['src/imaginary_module.py'])` β†’ `None`. file_patterns glob match 만 되면 톡과가 μ•„λ‹ˆλΌ content_patterns (`html_to_slide_mdx`/`edited_html_to_mdx`/`reverse_path`) κΉŒμ§€ hit ν•΄μ•Ό alert. false-positive 차단. - **JSON schema**: `{"generated_at": "", "registry": "docs/architecture/DORMANT-TRIGGERS.yaml", "alerts": []}` β€” u3 orchestrator helper κ°€ parse κ°€λŠ₯ν•œ stable shape. Stage 2 plan `tests:` (`test_check_script_clean_tree_no_alerts`, `test_check_script_writes_alerts_on_match`, `test_check_script_skips_manual_evidence_entries`) κ°€ u4 μ—μ„œ 직접 assert 둜 hook-up κ°€λŠ₯ν•œ surface (load_registry / collect_changed_files / check_entry / write_alerts 뢄리). β–  **remaining_units** (Stage 2 plan μˆœμ„œ μœ μ§€) - `u3` β€” `orchestrator.py` `_check_dormant_triggers()` helper + Stage 4β†’5 branch (P4a 미러; audit-only skip; informational supplement comment; never blocks) - `u4` β€” `tests/orchestrator_unit/test_dormant_triggers.py` β‰₯ 8 cases (IMP-# trigger 별 λͺ…λͺ…) - `u5` β€” `docs/architecture/PROJECT-INTENT-AND-GOVERNANCE.md` Β§ 8 anti-patterns row 에 1 line μΆ”κ°€ (registry 가리킀기) β–  **follow_up_issue_candidates**: μ—†μŒ. u2 λ²”μœ„ λ‚΄ scope-lock μ™Έ axis 미발견. β–  **guardrails 확인** - RULE 6 (commit scope only) β€” λ³Έ turn 은 commit/push μ—†μŒ (Stage 3 contract). 파일 1 개만 μž‘μ„±. λΆ€μ‚°λ¬Ό (`.orchestrator/dormant_alerts.json` smoke μ‚°μΆœ, `.orchestrator/tmp/u2_smoke.py`) λŠ” untracked + temp μ˜μ—­, commit λŒ€μƒ μ•„λ‹˜. - RULE 7 (no hardcoding) β€” 4 dormant ν•­λͺ© list λ₯Ό script κ°€ hardcode ν•˜μ§€ μ•ŠμŒ. registry yaml 만 읽음. u1 ↔ u2 single-source 계약 μœ μ§€. - PZ-1 (AI=0 normal) β€” λ³Έ unit deterministic only, LLM 호좜 0. - Stage 1 scope-lock β€” `scripts/check_dormant_triggers.py` 단일 파일, λ‹€λ₯Έ unit μ˜μ—­ (orchestrator.py / tests/orchestrator_unit/ / governance doc) λ―Έν„°μΉ˜. β–  **commit_sha**: HEAD = `8c1e563` (Stage 3 μ§„ν–‰ 쀑, 미컀밋 μƒνƒœ). rewind_target: continue_same