Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
# Changelog

## 2.3.0 — 2026-06-03 — Robust git-hook registration + lefthook support

Git hooks could silently stop firing — most visibly the `post-commit`
`local_evidence.py` emitter, so **contribution evidence stopped landing on
items** — whenever a hook slot was already occupied (typically by lefthook,
which owns `.git/hooks/`) or after a plugin update left a stale snapshot.
Registration is now robust and hook-manager-aware.

### Fixed
- `project_setup.install_hooks` no longer skips a slot with a blunt
`not dst.exists()` guard. EDPA marks its hooks with an `EDPA-MANAGED-HOOK`
sentinel and decides per slot: install when missing, refresh when EDPA-owned,
and **never clobber a foreign hook** (it warns + prints the exact chain-in
line instead).
- The SessionStart auto-update (`update_engine.sh`) now **re-registers EDPA git
hooks after a version bump** when the project already uses them — fixing
"hooks gone / contribution stopped after update". Opt-out repos stay untouched.

### Added
- **lefthook support.** When a `lefthook.yml` (or other lefthook config) is
present, `--with-hooks` detects it and prints a paste-ready lefthook snippet
(with `use_stdin: true` on `pre-push` — required, or lefthook hangs the push)
instead of writing `.git/hooks/`. EDPA never edits your lefthook config.
- `project_setup.py --check-hooks` — a read-only hooks doctor (active / missing
/ foreign / lefthook) — and `--refresh-hooks` — register/refresh only.

### Changed
- `scripts/hooks/install.sh` is now a thin delegator to
`project_setup.py --refresh-hooks`; the old conflicting `core.hooksPath`
mechanism (and its stale `.claude/edpa/...` path) is gone.
- ANSI colour codes in `project_setup.py` are suppressed when stdout is not a
TTY (no escape-code leak into captured SessionStart output).
- Removed the dead generic `pre-commit` hook (superseded by `pre-commit-id-safety`).
- Docs + website (CZ/EN) now document hook registration, the lefthook snippet,
foreign-hook behavior, and `--check-hooks` verification.

### Tests
- New `tests/test_project_setup_hooks.py` (fresh install, refresh, foreign-skip,
lefthook snippet validity) plus self-heal cases in
`tests/test_update_engine_hook.py`. Full suite green.

## 2.2.1 — 2026-06-01 — Skill names: drop the redundant `edpa-` prefix; server + create-pi are command-only

Plugin skill invocations were doubly namespaced — `/edpa:edpa-setup`,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ All invariants passed: YES

## Key Features

- **Zero manual input** — hours derived from **local git evidence**: post-commit hook emits `commit_author` + `/contribute` signals; engine reads `yaml_edit`, gate-event, and in-flight Story activity directly from `git log`.
- **Zero manual input** — hours derived from **local git evidence**: post-commit hook emits `commit_author` + `/contribute` signals; engine reads `yaml_edit`, gate-event, and in-flight Story activity directly from `git log`. Hooks register into `.git/hooks/` (or, under lefthook, via a printed snippet) and can be verified with `project_setup.py --check-hooks`.
- **Mathematical guarantee** — derived hours always sum to declared capacity
- **Gates mode (default)** — credits each Initiative/Epic/Feature status transition as a mini-deliverable, so prep work (LBC, decomposition, design) gets credited as it happens, not only at final Done. Validated to ±0.35 pp stability under ±20 % CW perturbation across 100 Monte Carlo runs.
- **C7.5 in-flight Story credit** — Stories with `yaml_edit` activity in the iteration window receive partial credit (`js × credit_factor`, default 0.40) even before they reach Done; the `story_activity_events[]` audit log in `edpa_results.json` records what was credited and why.
Expand Down
2 changes: 1 addition & 1 deletion docs/E2E-TEST-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ Plán je úspěšně provedený, pokud:
| `.edpa/engine/scripts/calibrate_signals.py` | auto-kalibrace CW vah (Monte Carlo + coord descent) |
| `.edpa/engine/scripts/hooks/commit-msg-ticket-attached` | commit-msg hook — vyžaduje item ref / escape |
| `.edpa/engine/scripts/hooks/post-commit-evidence` | post-commit hook — emituje evidence |
| `.edpa/engine/scripts/hooks/pre-commit` | pre-commit hook — schema + ID safety |
| `.edpa/engine/scripts/hooks/pre-commit-id-safety` | pre-commit hook — ID safety |
| `plugin/edpa/templates/github-workflows/edpa-contribution-sync.yml` | (volitelně) jediný V2 GH Action |
| `plugin/hooks/hooks.json` | Claude Code hooks (validate, commit info) |
| `plugin/.claude-plugin/plugin.json` | plugin manifest (single source of truth verze) |
Expand Down
72 changes: 66 additions & 6 deletions docs/RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ engine + `.edpa/` tree.

**Flags (all recommended for team workflows):**
- `--with-hooks` — pre-commit + commit-msg + post-commit + pre-push git hooks
(ID safety, ticket-attached, local `commit_author` evidence emission).
(ID safety, ticket-attached, local `commit_author` evidence emission). Detects
lefthook and prints a paste-ready snippet instead of touching `.git/hooks/`;
never clobbers a foreign hook. See **Git hooks** below.
- `--with-ci` — copies `edpa-contribution-sync.yml`; materializes PR-thread
signals (`pr_reviewer`, `issue_comment`) into `evidence[]` after merge.
Optional, GitHub-only — local commit evidence flows without it.
Expand All @@ -86,7 +88,7 @@ engine + `.edpa/` tree.
**Expected output (last steps):**

```
[1] Vendor engine ✓ Vendored engine → .edpa/engine/ (37 scripts, VERSION 2.1.9)
[1] Vendor engine ✓ Vendored engine → .edpa/engine/ (37 scripts, VERSION 2.3.0)
[2] Directory tree ✓ Directory tree at .edpa/
[3] Config templates ✓ Seeded people.yaml, edpa.yaml, cw_heuristics.yaml
[4] ID counter ✓ id_counters.yaml seeded
Expand All @@ -101,8 +103,61 @@ EDPA setup complete.
hand-edit; the SessionStart hook re-syncs it on plugin update).
- `.edpa/config/{edpa.yaml,people.yaml,cw_heuristics.yaml,id_counters.yaml}`.
- `.edpa/{backlog,iterations,reports,snapshots}/` tree.
- (flags) `.git/hooks/*`, `.github/workflows/edpa-contribution-sync.yml`,
`.claude/rules/`.
- (flags) `.git/hooks/*` (or a lefthook snippet),
`.github/workflows/edpa-contribution-sync.yml`, `.claude/rules/`.

**Git hooks — registration, lefthook, verification:**

`--with-hooks` writes four hooks into `.git/hooks/` (`pre-commit`, `pre-push`,
`commit-msg`, `post-commit`). The `post-commit` one runs `local_evidence.py` —
**this is what records contribution evidence onto items**, so if it isn't
registered, contributions silently never appear. The registration is
deliberately careful:

- **Re-running is safe and self-refreshing.** Re-run `/edpa:setup --with-hooks`
(or `python3 .edpa/engine/scripts/project_setup.py --refresh-hooks`) any time:
EDPA-owned hooks are overwritten with the current version, missing ones
reinstalled. EDPA marks its hooks with an `EDPA-MANAGED-HOOK` sentinel.
- **Foreign hooks are never clobbered.** If a non-EDPA file already occupies a
slot, EDPA skips it and prints a loud warning with the exact line to chain
EDPA in by hand (`sh .edpa/engine/scripts/hooks/<hook> "$@"`).
- **lefthook (or any tool that owns `.git/hooks/`).** lefthook generates its own
dispatcher shims into `.git/hooks/` (and can set `core.hooksPath`), so a plain
copy would be ignored or clobbered — this is the usual cause of "contribution
stopped working after an update". EDPA detects `lefthook.yml` and, instead of
writing `.git/hooks/`, prints a paste-ready block. Add it to your
`lefthook.yml`, then run `lefthook install`:

```yaml
pre-commit:
commands:
edpa-id-safety:
run: sh .edpa/engine/scripts/hooks/pre-commit-id-safety
commit-msg:
commands:
edpa-ticket-attached:
run: sh .edpa/engine/scripts/hooks/commit-msg-ticket-attached {1}
post-commit:
commands:
edpa-evidence:
run: sh .edpa/engine/scripts/hooks/post-commit-evidence
pre-push:
commands:
edpa-id-safety:
run: sh .edpa/engine/scripts/hooks/pre-push-id-safety {1} {2}
use_stdin: true # pre-push refs arrive on stdin — without this lefthook hangs
```

- **After a plugin update**, the SessionStart auto-update re-registers EDPA hooks
automatically when the project already uses them (and, under lefthook, reminds
you to verify). No manual step needed for the plain `.git/hooks/` case.
- **Verify any time** (read-only doctor — no changes):

```bash
python3 .edpa/engine/scripts/project_setup.py --check-hooks
```
Reports each hook as active / missing / foreign, or flags lefthook so you know
to register via the snippet.

**Next:** edit `people.yaml` (your team) + `edpa.yaml` (`project.name`), then
create items locally:
Expand Down Expand Up @@ -367,11 +422,16 @@ git add .github/workflows/edpa-collision-check.yml
git commit -m "ci: add EDPA collision check"
```

Verify hooks are installed:
Verify hooks are installed (read-only doctor — works for `.git/hooks/` and
flags lefthook):
```bash
ls -la .git/hooks/pre-commit .git/hooks/pre-push # must be -rwxr-xr-x
python3 .edpa/engine/scripts/project_setup.py --check-hooks
```

If your repo uses **lefthook**, `--with-hooks` prints a paste-ready snippet
instead of writing `.git/hooks/`; add it to `lefthook.yml` and run
`lefthook install` (see §1 → *Git hooks — registration, lefthook, verification*).

See [`docs/dev-collisions.md`](dev-collisions.md) for decision tree, common collision shapes (single / multi / parent-chain / cascading), troubleshooting, and the `--target develop` flag for Git Flow projects.

---
Expand Down
2 changes: 1 addition & 1 deletion docs/methodology.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

*Capacity derivation from delivery evidence*

**Version 2.2.1 — June 2026 — Jaroslav Urbanek, Lead Architect**
**Version 2.3.0 — June 2026 — Jaroslav Urbanek, Lead Architect**

---

Expand Down
33 changes: 29 additions & 4 deletions docs/playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ V terminalu s Claude Code nainstalovanym:
/edpa:setup --with-ci --with-hooks --with-rules
```

Claude Code (skill `/edpa:setup`) provede kroky 1.1-1.4 automaticky -- vendoruje engine do `.edpa/engine/`, naseje konfiguraci a `id_counters.yaml`, a volitelne nainstaluje git hooky, PR-signal CI workflow a `.claude/rules/`. Idempotentni -- opakovane spusteni nic nerozbije.
Claude Code (skill `/edpa:setup`) provede kroky 1.1-1.4 automaticky -- vendoruje engine do `.edpa/engine/`, naseje konfiguraci a `id_counters.yaml`, a volitelne nainstaluje git hooky, PR-signal CI workflow a `.claude/rules/`. Idempotentni -- opakovane spusteni nic nerozbije. `--with-hooks` je lefthook-aware (pri pritomnem `lefthook.yml` vypise snippet misto zapisu do `.git/hooks/`); stav overis pres `--check-hooks`.

### Cesta B: Manualni CLI

Expand All @@ -101,6 +101,8 @@ curl -fsSL https://edpa.technomaton.com/install.sh | sh
python3 .edpa/engine/scripts/project_setup.py --with-ci --with-hooks --with-rules
```

> `--with-hooks` je lefthook-aware (pri pritomnem `lefthook.yml` vypise paste-ready snippet misto zapisu do `.git/hooks/`); stav hooku overis pres `--check-hooks`.

Vysledna struktura:

```
Expand Down Expand Up @@ -446,7 +448,30 @@ Povolene prefixy: `S` (Story), `F` (Feature), `E` (Epic), `T` (Task), `D` (Defec
| pre-commit | ID safety -- kontrola referenci |
| commit-msg | Vyzaduje referenci itemu nebo `no-ticket:` |
| post-commit | Zaznamenava `commit_author` evidence |
| pre-push | Kontrola ID kolizi |
| pre-push | Kontrola ID kolizi vuci remote |

Registrace je idempotentni a sebe-obnovujici: EDPA znacka sve hooky sentinelem `EDPA-MANAGED-HOOK`, opakovany `--with-hooks` (nebo `--refresh-hooks`) je osvezi a cizi (ne-EDPA) hook v dane pozici **nikdy nepreplacne** -- vypise hlasku + radek k rucnimu zaretezeni (`sh .edpa/engine/scripts/hooks/<hook> "$@"`). Pokud je v repu **lefthook** (`lefthook.yml`), `.git/hooks/` vlastni lefthook, takze EDPA tam nezapisuje a misto toho vypise hotovy snippet do `lefthook.yml`; pote spust `lefthook install`. Stav hooku overis read-only pres `--check-hooks` (kazdy hook jako active / missing / foreign, pripadne flag lefthook).

```yaml
# lefthook.yml -- EDPA hooky (pre-push MUSI mit use_stdin: true, jinak push zatuhne)
pre-commit:
commands:
edpa-id-safety:
run: sh .edpa/engine/scripts/hooks/pre-commit-id-safety
commit-msg:
commands:
edpa-ticket-attached:
run: sh .edpa/engine/scripts/hooks/commit-msg-ticket-attached {1}
post-commit:
commands:
edpa-evidence:
run: sh .edpa/engine/scripts/hooks/post-commit-evidence
pre-push:
commands:
edpa-id-safety:
run: sh .edpa/engine/scripts/hooks/pre-push-id-safety {1} {2}
use_stdin: true
```

**Commit konvence:**

Expand Down Expand Up @@ -782,13 +807,13 @@ V2 ma **jediny** volitelny workflow (jen s `--with-ci`):
- [ ] Backlog naplneny pres `backlog.py add` (alespon 1 Epic, 3 Features, 10 Stories)
- [ ] `backlog.py validate` projde bez chyb
- [ ] `engine.py --demo` projde uspesne
- [ ] (volitelne) git hooky nainstalovany (`--with-hooks`), contribution-sync CI (`--with-ci`)
- [ ] (volitelne) git hooky nainstalovany (`--with-hooks`) a overeny (`--check-hooks` -> vsechny active; pri lefthooku snippet v `lefthook.yml` + `lefthook install`), contribution-sync CI (`--with-ci`)

### Tyden 1

- [ ] Tym pracuje s branch naming konvenci (`feature/S-XXX-popis`)
- [ ] Commity referuji work items (`feat(S-XXX): ...`)
- [ ] post-commit hook zaznamenava `commit_author` evidenci
- [ ] post-commit hook zaznamenava `commit_author` evidenci (overeno `--check-hooks`; je-li post-commit `missing`/`foreign`, contribution evidence se tise nezapisuji -- u lefthooku doplnit snippet + `lefthook install`)
- [ ] (volitelne) PR reviews probihaji a contribution-sync je materializuje

### Konec iterace 1
Expand Down
6 changes: 5 additions & 1 deletion docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ If your team has more than one person creating backlog items in parallel, you'll
**Setup** (one-time per project):

```bash
# 1. Install pre-commit + pre-push hooks
# 1. Install git hooks (pre-commit, pre-push, commit-msg, post-commit).
# Under lefthook this prints a snippet to paste into lefthook.yml + run
# `lefthook install` instead of writing .git/hooks/. Foreign hooks are
# never overwritten; re-run any time to refresh.
python3 .edpa/engine/scripts/project_setup.py --with-hooks
python3 .edpa/engine/scripts/project_setup.py --check-hooks # verify (read-only)

# 2. Copy CI workflow template
cp .edpa/engine/templates/github-workflows/edpa-collision-check.yml \
Expand Down
2 changes: 1 addition & 1 deletion plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "edpa",
"version": "2.2.1",
"version": "2.3.0",
"description": "EDPA — Evidence-Driven Proportional Allocation. Derive hours from local git evidence (commits, yaml edits, status transitions). Zero timesheets, mathematical guarantee, Monte Carlo calibrated CW weights. Local-first: .edpa/backlog/ YAML as source of truth, git as the audit trail. GitHub Projects sync optional.",
"author": {
"name": "TECHNOMATON",
Expand Down
4 changes: 3 additions & 1 deletion plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ plugin/
│ ├── validate_on_save.sh # PostToolUse Edit|Write — YAML/JSON syntax check
│ ├── edpa_post_commit.sh # PostToolUse Bash — commit info
│ ├── pre-commit # Git pre-commit (user-installed, not auto-wired)
│ └── install.sh # Helper to install pre-commit into .git/hooks/
│ └── install.sh # Thin delegator → project_setup.py --refresh-hooks (lefthook-aware; old core.hooksPath mechanism removed)
├── schemas/
│ └── edpa_commit_info.schema.json
├── templates/
Expand Down Expand Up @@ -141,6 +141,8 @@ When teams have multiple devs creating backlog items in parallel branches, ID co
| 7 — CI workflow | server | PR open/sync | `edpa-collision-check.yml` (comments on PR + fails check) |
| Recovery | local | after conflict | `renumber_collisions.py --apply` (renames + updates parents + bumps counter) |

Hook registration (`--with-hooks` / `--refresh-hooks`) is idempotent and lefthook-aware: EDPA tags its own hooks with an `EDPA-MANAGED-HOOK` sentinel, never clobbers a foreign hook already in a slot, and — if a `lefthook.yml` is present — prints a paste-ready snippet instead of writing to `.git/hooks/`. Verify any time with the read-only doctor `project_setup.py --check-hooks` (reports each hook as active / missing / foreign, or flags lefthook).

**Quick setup** (one-time per project):

```bash
Expand Down
1 change: 1 addition & 0 deletions plugin/edpa/scripts/hooks/commit-msg-ticket-attached
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/sh
# EDPA-MANAGED-HOOK
# EDPA V2.1 commit-msg hook — require an EDPA item reference (or escape).
#
# Git invokes this with $1 = path to COMMIT_EDITMSG. Exit non-zero
Expand Down
25 changes: 16 additions & 9 deletions plugin/edpa/scripts/hooks/install.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
#!/bin/bash
# Install EDPA git hooks
ROOT="$(git rev-parse --show-toplevel)"
HOOKS_DIR="$ROOT/.claude/edpa/scripts/hooks"
#!/bin/sh
# Thin delegator — the real EDPA git-hook registration lives in
# project_setup.py (script-first: one implementation, thin callers). Kept for
# the documented `sh .edpa/engine/scripts/hooks/install.sh` path.
#
# It installs into .git/hooks/ (ownership-tracked, foreign hooks left alone)
# or prints a paste-ready snippet when lefthook is detected. The old
# `git config core.hooksPath` mechanism is gone: it pointed at a stale path
# and silently fought lefthook / the .git/hooks/ copy path.
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
[ -z "$ROOT" ] && { echo "ERROR: not a git repository." >&2; exit 1; }

if [ ! -d "$HOOKS_DIR" ]; then
echo "ERROR: Hooks directory not found at $HOOKS_DIR"
echo "Make sure the EDPA plugin is installed."
SETUP="$ROOT/.edpa/engine/scripts/project_setup.py"
if [ ! -f "$SETUP" ]; then
echo "ERROR: EDPA engine not found at $SETUP" >&2
echo "Run /edpa:setup (or the curl|sh installer) to vendor the engine first." >&2
exit 1
fi

git config core.hooksPath "$HOOKS_DIR"
echo "Git hooks installed from $HOOKS_DIR"
exec python3 "$SETUP" --refresh-hooks --root "$ROOT"
1 change: 1 addition & 0 deletions plugin/edpa/scripts/hooks/post-commit-evidence
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/sh
# EDPA-MANAGED-HOOK
# EDPA V2.1 local-evidence post-commit hook.
#
# Runs after every commit. Detects item refs in the commit (msg + paths),
Expand Down
31 changes: 0 additions & 31 deletions plugin/edpa/scripts/hooks/pre-commit

This file was deleted.

5 changes: 3 additions & 2 deletions plugin/edpa/scripts/hooks/pre-commit-id-safety
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/bin/sh
# EDPA-MANAGED-HOOK
# EDPA V2 ID safety pre-commit hook — validates staged .edpa/backlog/ files.
# Layer 5 from docs/v2/plan.md.
#
# Install: symlink to .git/hooks/pre-commit-id-safety, then chain from
# .git/hooks/pre-commit (or use install.sh's --with-id-hooks flag).
# Install: /edpa:setup --with-hooks (or project_setup.py --with-hooks). Detects
# lefthook and prints a paste-ready snippet instead of writing .git/hooks/.
#
# Exit 1 to block commit on any ID safety violation.

Expand Down
4 changes: 3 additions & 1 deletion plugin/edpa/scripts/hooks/pre-push-id-safety
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/bin/sh
# EDPA-MANAGED-HOOK
# EDPA V2 ID safety pre-push hook — verifies no ID collisions with upstream.
# Layer 6 from docs/v2/plan.md.
#
# Install: symlink to .git/hooks/pre-push.
# Install: /edpa:setup --with-hooks. Under lefthook, the command needs
# `use_stdin: true` (push refs arrive on stdin, read below).
#
# Git invokes this with two args: remote_name and remote_url, and pipes
# the push refs on stdin (local_ref local_sha remote_ref remote_sha).
Expand Down
Loading
Loading