Skip to content

BACK-494 - Backlog-Guard: PreToolUse hook to enforce MCP/CLI access to backlog directories#649

Open
lenucksi wants to merge 49 commits into
MrLesk:mainfrom
lenucksi:feature/back-494-backlog-guard-hook
Open

BACK-494 - Backlog-Guard: PreToolUse hook to enforce MCP/CLI access to backlog directories#649
lenucksi wants to merge 49 commits into
MrLesk:mainfrom
lenucksi:feature/back-494-backlog-guard-hook

Conversation

@lenucksi
Copy link
Copy Markdown

Summary

  • Adds hooks/backlog-guard/ — a Claude Code PreToolUse hook that hard-blocks direct Read/Edit/Write/Bash access to configured backlog directories and redirects agents to the correct MCP tool or CLI command with pre-filled parameters (task ID extracted from filename, document name, etc.)
  • Config via .backlog-guard YAML file at git root, auto-discovered via git rev-parse --show-toplevel; falls back to detecting backlog/config.yml in the directory tree (no configuration needed for standard single-backlog projects)
  • Modelled after serena-guard — same hard-blocking pattern, same hook output format; difference is path-prefix detection instead of file-extension detection
  • Adds .codex/skills/backlog-guard-setup/SKILL.md — a one-command setup skill that auto-detects the backlog directory, creates .backlog-guard, writes the hook entry into the appropriate settings file, and optionally adds mcp__backlog__* tool permissions to the allowlist
  • Adds __pycache__/ and *.pyc to .gitignore

Test plan

  • python -m pytest hooks/backlog-guard/test_check.py -v — 8 tests pass
  • Manual: Read on backlog/tasks/back-1-...md → denied, BACK-1 in reason, MCP + CLI suggestion in context
  • Manual: Read on src/cli.ts → allowed (not in backlog dir)
  • Manual: Bash(cat backlog/tasks/back-1-...md) → denied
  • Manual: grep pattern file.txt | grep back-123 → allowed (pipeline stdin, not file arg)
  • Manual: no .backlog-guard, backlog/config.yml present → auto-detect still blocks
  • Manual: /backlog-guard-setup skill → .backlog-guard created, settings hook entry written

Block message example

When an agent tries Read(backlog/tasks/back-494 - Backlog-Guard-....md):

⛔ BACKLOG GUARD — Read on task file (BACK-494) is forbidden.
All backlog data access must go through MCP tools or the backlog CLI.
Target: /path/to/backlog/tasks/back-494 - Backlog-Guard-....md

USE ONE OF THESE INSTEAD:
MCP:  mcp__backlog__task_view(id="BACK-494")
CLI:  backlog task BACK-494

Protected directory: /path/to/backlog
Config: /path/to/.backlog-guard

🤖 Generated with Claude Code

lenucksi and others added 30 commits May 6, 2026 22:19
- Add terminalStatuses?: string[] to BacklogConfig interface
- Parse terminal_statuses key in config loader (same pattern as statuses)
- Extend getTerminalStatus/isTerminalStatus with optional terminalStatuses param
  - Falls back to last-element convention when not provided (backward-compatible)
  - isTerminalStatus returns true for any entry in terminalStatuses array
  - getTerminalStatus returns terminalStatuses[0] as primary terminal status
- Add 5 new test cases covering multi-terminal, fallback, empty-array, and
  non-last-element override scenarios

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BACK-465 - Config-Schema: terminalStatuses-Key einführen
…atistics, milestones, handlers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uences and board filters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x serializeConfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added 4 tests to src/web/lib/lanes.test.ts covering the sortTasksForStatus
refactoring from BACK-467: custom terminal status gets done-sort even when
not last in statuses array, empty terminalStatuses falls back to last-element
convention, and active statuses are unaffected when terminalStatuses is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tyling

- Add blockedStatuses?: string[] to BacklogConfig type
- Parse/serialize blocked_statuses in parseConfig/serializeConfig (same pattern as terminalStatuses)
- Extend getStatusStyle(), getStatusColor(), getStatusIcon(), formatStatusWithIcon() to accept optional blockedStatuses param
- Config-aware check runs first; falls back to exact-match statusMap ("Blocked") and substring heuristic (includes("blocked"))
- Thread blockedStatuses prop through App.tsx -> BoardPage -> Board -> TaskColumn
- Update getStatusBadgeClass() in TaskColumn: config array checked first, then fallback includes("blocked")/includes("stuck")
- Add 3 new tests (RED confirmed before implementation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…HEAD

Replace git rev-list --all --count with git rev-list --count HEAD in
getCommitCountInTest. The --all flag includes refs/notes/ai commits
created asynchronously by the global git-ai trace2 tool, inflating
counts non-deterministically. HEAD-scoped counting matches the approach
in cli.test.ts and correctly measures commits on the current branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…HEAD

Replace git rev-list --all --count with git rev-list --count HEAD in
getCommitCountInTest. The --all flag includes refs/notes/ai commits
created asynchronously by the global git-ai trace2 tool, inflating
counts non-deterministically. HEAD-scoped counting matches the approach
in cli.test.ts and correctly measures commits on the current branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Commit all untracked backlog task files (back-465 through back-482)
and update modified task files (back-200 through back-438) to
establish a safe baseline before upstream PR rebase operations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Upstream version-bump CI commit used 2-space indent; reformat to tabs
to match the project Biome config and unblock `bun run check .`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renamed back-466/back-467 task files from German to English names.
Translated implementation notes in back-469 to English.
BACK-480 marked Done with full notes and final summary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exclude package.json from Biome's formatter via overrides to prevent
tabs-vs-spaces conflicts on every pull from upstream, which uses spaces.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
style.css is rebuilt by build:css before every build/cli invocation;
committing it caused noisy 200KB single-line diffs on any CSS change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New tasks:
- BACK-486: Colored Labels (WebUI/TUI/CLI/MCP/REST)
- BACK-487: Labels for Documents and Decisions
- BACK-488: Label Management — central CRUD + autocomplete
- BACK-489: Archive Documents and Decisions + superseded ADRs
- BACK-490: REST API OpenAPI self-documentation
- BACK-491: Cross-Modality CI — enforce feature parity
- BACK-492: Tech Debt Research — codebase audit

Updated:
- BACK-474: recycled from German stub → Sequence/Dependency Visualizer Research (audits BACK-217/218 cluster, library eval, no code)
- BACK-484: fleshed out assignee autocomplete stub with description + AC (scraping-based, no central config)

All tickets include upstream-master constraint block and explicit 5-modality AC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BACK-466/467: translate implementation notes and titles to English (BACK-480 follow-up)
- BACK-479/481/482: mark Done, check off all ACs, add Final Summary sections
- BACK-478: remove task (superseded by BACK-479–482)
- BACK-483: add new task (subtasks / Epic-level tracking question)
- .gitignore: ignore archive/ directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the milestones page sorted milestone cards by ID descending
(newest first), so a project with phases m-3 through m-6 would display
Phase 9 (m-6) at the top, followed by Phase 7 (m-4) and Phase 8 (m-5).

This reverses the sort to ascending so milestones appear in their natural
creation order (m-1 → m-2 → m-3 … ), matching the sequence users intend
when they create milestones in phase/sprint order.
The task list view (web UI + CLI) previously only supported sorting by
id and priority. Tasks have an ordinal field used for manual wave-based
ordering, which the board view already respects — but the list view
ignored it entirely, showing tasks in ID order with no way to recover
the deliberate sequence.

Changes:
- TaskList.tsx: add "ordinal" to TaskSortColumn union type; add ordinal
  sort case in sortedDisplayTasks useMemo (tasks with ordinals come
  first, then ascending by ordinal value, matching board view behavior);
  add sortable "Ordinal" column header and data cell in the table
- cli.ts: add "ordinal" to validSortFields in all four task list/draft
  list call sites; update --sort option help text accordingly

Closes MrLesk#643
…o backlog dirs

Adds hooks/backlog-guard/ — a hard-blocking PreToolUse hook that intercepts
Read/Edit/Write/Bash operations on files inside configured backlog directories
and redirects the agent to the correct MCP tool or CLI command.

Config via .backlog-guard YAML at git root (auto-discovered via git rev-parse);
falls back to auto-detecting backlog/config.yml in the directory tree. Block
messages include the exact MCP call and CLI command, with task ID extracted from
the filename (back-NNN-title.md) for task files.

Also adds .codex/skills/backlog-guard-setup/SKILL.md — a setup skill that
auto-detects the backlog directory, creates .backlog-guard, writes the hook entry
into the appropriate settings file, and optionally adds mcp__backlog__* allowlist
entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aths

- Add Grep tool handling: block when path resolves under protected dir;
  suggest task_search for tasks, document_search for docs
- Write with no task ID in filename now suggests task_create instead of task_edit;
  Write to new doc path suggests document_create instead of document_update
- classify_path now covers completed/, drafts/, decisions/ directories
- _generic_suggestions handles decision type (search suggestion)
- Matcher updated to Read|Edit|Write|Bash|Grep in README and setup skill
- 4 new tests (12 total, all passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hooks/backlog-guard introduces .py and .sh files; adding these languages
enables LSP support for the new hook files in the Serena project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hooks/backlog-guard introduces .py and .sh files; adding these languages
enables LSP support for the new hook files in the Serena project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lenucksi and others added 7 commits May 13, 2026 19:53
Add isPortAvailable() and findNextAvailablePort() to server/index.ts,
pre-check port in CLI browser command, prompt user to start on next
free port if taken. Simplify EADDRINUSE catch (UX now in CLI).
# Conflicts:
#	backlog/tasks/back-473 - handle-port-congestion-for-backlog-browser.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Code and OpenCode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lenucksi lenucksi force-pushed the feature/back-494-backlog-guard-hook branch from 05c0f69 to 17a4aab Compare May 14, 2026 13:50
lenucksi and others added 11 commits May 14, 2026 15:54
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cate cleanup

- Created 6 new milestones (m-7 through m-13) covering Forge Integration,
  Web UI Polish, Task Schema & Metadata, Subtasks & Dependencies,
  Agent & IDE Integrations, CLI & TUI Enhancements, and Infrastructure & DevEx
- Assigned all ~80 open tasks to milestones
- Closed BACK-222 and BACK-483 as duplicates of the BACK-493 cluster
- Marked BACK-420 as sub-concern of BACK-260; added cross-references
- Added dependency and relationship notes across BACK-239/477, BACK-349/200/310,
  and the BACK-474/217 sequence cluster
- Set priorities on all previously unprioritized open tasks (1× high, 14× medium, 16× low)
- Added dependency chains: 268→267, 487→486, 488→486, 504→500+503, 506→260
- Added BACK-506 (batch operations in All Tasks view) to Web UI Polish milestone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Research pass identifying DRY/KISS violations, pattern inconsistencies,
and static analysis tooling options. Output: doc-004 in backlog/docs/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Internal audit document (doc-004) — not for upstream PR.
4 additional DRY/KISS violations: core.fs vs core.filesystem accessor
inconsistency, duplicate ensureInitialized pattern, error surface
fragmentation across modalities, repeated listMilestones Promise.all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create doc-005 with full CLI/TUI/WebUI/MCP feature matrix across 9 domains.
Create BACK-492 subtasks .1–.7 from audit stubs A–G with full ACs and implementation plans.
Keep BACK-492 In Progress pending subtask completion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l summary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Milestone cards sort by ID ascending (m-1 before m-2). Tests
used [0] to pick the first Edit/Remove button but expected
m-2 (Release 2) behavior. Changed [0] to [1] to match actual
card ordering.
…Code and OpenCode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Claude Code + OpenCode, dual MCP naming, Claude Code plugin structure, marketplace, skills
@lenucksi lenucksi force-pushed the feature/back-494-backlog-guard-hook branch from 17a4aab to 0d84d25 Compare May 20, 2026 16:13
…publish workflow

- packages/backlog-guard/package.json - scoped npm package for OpenCode plugin
- .github/workflows/publish-backlog-guard.yml - publish on push/tag/manual
- dev.sh copies compiled opencode-plugin.js into the package
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant