# Root Cause Analysis: DES Not Installed When Installing via Marketplace

**Date**: 2026-03-08
**Analyst**: Rex (nw-troubleshooter)
**Severity**: Critical -- DES is the enforcement backbone of nWave
**Status**: Investigation complete

---

## Problem Statement

When nWave is installed via the Claude Code marketplace plugin system, the DES (Deterministic Execution System) appears not to be installed. DES enforces TDD phases, validates step files, and runs hooks -- without it, nWave has no enforcement mechanism.

**Scope**: Plugin build pipeline (`build_plugin.py`), plugin installation via Claude Code marketplace, comparison with custom installer (`des_plugin.py`).

---

## Executive Finding

**The DES IS included in the plugin build.** The `build_plugin.py` script bundles the DES module, generates hooks, and creates a wrapper. However, there are significant gaps between what the plugin provides and what the custom installer provides. The problem statement "DES not installed" is likely a symptom of one of several specific gaps documented below.

---

## Evidence: What the Plugin Build DOES Include

### DES Module (scripts/des/)
- **File**: `scripts/build_plugin.py`, lines 575-599 (`copy_des_module`)
- **Evidence**: The `build()` pipeline (line 787) calls `copy_des_module(config, plugin_dir)` which copies `src/des/` to `plugin/scripts/des/`, rewrites `src.des` imports to `des`, clears `__pycache__`, and validates Python syntax.
- **Destination**: `plugin/scripts/des/`

### DES Templates
- **File**: `scripts/build_plugin.py`, lines 602-620 (`copy_templates`)
- **Evidence**: Copies `step-tdd-cycle-schema.json` and `roadmap-schema.json` to `plugin/scripts/templates/`
- **Destination**: `plugin/scripts/templates/`

### Hooks Configuration (hooks.json)
- **File**: `scripts/build_plugin.py`, lines 623-654 (`generate_hooks_json`)
- **Evidence**: Generates `hooks/hooks.json` with 5 DES hook events
- **Destination**: `plugin/hooks/hooks.json`

### Hook Wrapper Script
- **File**: `scripts/build_plugin.py`, lines 657-669 (`generate_hook_wrapper`)
- **Evidence**: Generates `scripts/des-hook` Python wrapper that sets up PYTHONPATH and invokes `des.adapters.drivers.hooks.claude_code_hook_adapter.main()`
- **Destination**: `plugin/scripts/des-hook`

### Plugin Validation Includes DES
- **File**: `scripts/build_plugin.py`, lines 918-927 (`_validate_des_module`)
- **Evidence**: Validation checks for `scripts/des/__init__.py` existence
- **File**: `scripts/build_plugin.py`, lines 869-915 (`_validate_hooks`)
- **Evidence**: Validation checks hooks.json exists, is valid JSON, has proper schema

### Release Pipeline Builds and Validates Plugin
- **File**: `.github/workflows/release-prod.yml`, lines 293-309 (build-plugin job)
- **Evidence**: Runs `build_plugin.py`, then runs `validate()` on the output, then packages as ZIP, then validates ZIP integrity

---

## Toyota 5 Whys Analysis

### BRANCH A: Missing Write/Edit Guard Hooks

**WHY 1A**: Plugin hooks do not include PreToolUse guards for Write and Edit operations.
**Evidence**: `build_plugin.py` lines 501-507 define `_HOOK_EVENTS` as:
```python
_HOOK_EVENTS = (
    ("PreToolUse", "Task", "pre-task"),
    ("PostToolUse", "Task", "post-tool-use"),
    ("SubagentStop", None, "subagent-stop"),
    ("SessionStart", "startup", "session-start"),
    ("SubagentStart", None, "subagent-start"),
)
```
Only ONE PreToolUse hook with matcher "Task" is generated. No Write or Edit matchers.

Compare with `des_plugin.py` lines 556-605, which generates THREE PreToolUse entries:
1. `matcher: "Agent"` (pre-task)
2. `matcher: "Write"` (pre-write with fast-path shell logic)
3. `matcher: "Edit"` (pre-edit with fast-path shell logic)

**WHY 2A**: The plugin build was designed before the Write/Edit guard hooks were added to the custom installer.
**Evidence**: The plugin build uses a static `_HOOK_EVENTS` tuple (line 501) that was defined when only Agent/Task interception was needed. The Write/Edit guards were added to `des_plugin.py` later (lines 567-605) with sophisticated shell fast-path logic, but `build_plugin.py` was never updated to include them.

**WHY 3A**: The hook generation in `build_plugin.py` and `des_plugin.py` are completely independent implementations with no shared code.
**Evidence**: `build_plugin.py` uses `_HOOK_EVENTS` tuple + `generate_hook_config()` pure function (lines 496-525). `des_plugin.py` uses `HOOK_EVENTS` tuple + `_install_des_hooks()` method (lines 461-641). They share no code -- the hook definitions are duplicated.

**WHY 4A**: The architecture intentionally separated plugin and installer paths (ADR-001, ADR-002) but did not establish a mechanism to keep hook definitions synchronized.
**Evidence**: ADR-001 (line 59): "DES hook format differs between plugin (`${CLAUDE_PLUGIN_ROOT}`) and custom installer (`$HOME/.claude`), creating two code paths in hook generation". This was accepted as a known consequence without a synchronization mechanism.

**WHY 5A**: No automated test or CI check validates that the plugin's hook set is equivalent to the installer's hook set.
**Evidence**: The plugin build tests (`tests/build/acceptance/plugin/`) validate structure and format but do not cross-reference against `des_plugin.py` to ensure feature parity.

**ROOT CAUSE A**: Divergent hook definitions between two independent implementations (`build_plugin.py` vs `des_plugin.py`) with no synchronization mechanism or parity test.

---

### BRANCH B: Stale PreToolUse Matcher ("Task" instead of "Agent")

**WHY 1B**: The plugin's PreToolUse hook uses matcher "Task" instead of "Agent".
**Evidence**: `build_plugin.py` line 502: `("PreToolUse", "Task", "pre-task")`. Compare with `des_plugin.py` line 556: `"matcher": "Agent"`. Claude Code v2.1.63 renamed Task to Agent (per MEMORY.md). The matcher "Task" may or may not still work in current Claude Code versions.

**WHY 2B**: The `build_plugin.py` was written before the Task-to-Agent migration.
**Evidence**: MEMORY.md states: "Task->Agent migration (2026-03-05): Claude Code v2.1.63 renamed Task->Agent". The `_HOOK_EVENTS` tuple in `build_plugin.py` still references "Task".

**WHY 3B**: Same as WHY 3A -- independent implementations with no shared definition.

**WHY 4B**: Same as WHY 4A.

**WHY 5B**: Same as WHY 5A.

**ROOT CAUSE B**: Same root as A -- divergent implementations. Specific manifestation: stale "Task" matcher not updated to "Agent".

---

### BRANCH C: No DES Configuration Bootstrap (.nwave/des-config.json)

**WHY 1C**: The plugin does not create `.nwave/des-config.json` in the project directory.
**Evidence**: `build_plugin.py` has no function equivalent to `des_plugin.py`'s `_bootstrap_des_config()` (lines 713-738). The custom installer creates `.nwave/des-config.json` with audit logging defaults. The plugin cannot do this -- it is a static artifact copied to `~/.claude/plugins/cache/`.

**WHY 2C**: The DES config is project-scoped (lives in `.nwave/`), not plugin-scoped. Plugins are installed globally, not per-project.
**Evidence**: `des_plugin.py` line 723: "The config lives in the project directory (.nwave/), not ~/.claude, because audit log paths are project-relative."

**WHY 3C**: Claude Code's plugin system has no "post-install" hook mechanism that could bootstrap project-level configuration.
**Evidence**: Plugin docs (Finding 11 in research doc) show plugins are static: they provide components (agents, skills, commands, hooks) but cannot execute arbitrary scripts at install time. ADR-001 line 44: "Plugins are sandboxed -- they cannot run arbitrary install scripts."

**WHY 4C**: The DES hook adapter has a fallback for missing config but may behave differently.
**Evidence**: `claude_code_hook_adapter.py` line 74: `DESConfig()` is created. If `.nwave/des-config.json` is missing, the adapter likely uses defaults. This is a softer gap -- DES may still work but audit logging may not initialize on first use.

**WHY 5C**: The plugin architecture inherently cannot bootstrap project-level state because plugins are global static artifacts.

**ROOT CAUSE C**: Architectural limitation -- Claude Code plugin system cannot perform project-level bootstrapping. The DES config gap is inherent to the plugin distribution model. **Impact**: Low-medium. DES hooks likely use sensible defaults when config is absent. The `_create_audit_writer()` function (line 66-77 of `claude_code_hook_adapter.py`) creates `DESConfig()` which reads from file if present, presumably falls back to defaults.

---

### BRANCH D: DES Utility Scripts Not Included

**WHY 1D**: The plugin does not include DES utility scripts (`check_stale_phases.py`, `scope_boundary_check.py`).
**Evidence**: `build_plugin.py` copies the DES Python module (`src/des/`) and templates, but NOT the scripts from `nWave/scripts/des/`. Compare with `des_plugin.py` lines 23-26 (`DES_SCRIPTS`) and lines 341-378 (`_install_des_scripts`), which install these to `~/.claude/scripts/`.

**WHY 2D**: These scripts are supplementary tools, not part of the core DES enforcement loop. They are invoked manually or by pre-commit hooks, not by Claude Code hooks.

**WHY 3D**: The plugin build was scoped to include only what is needed for Claude Code hook enforcement, not ancillary tooling.

**ROOT CAUSE D**: Design choice -- utility scripts are out of scope for the plugin. **Impact**: Low. These are convenience tools, not enforcement-critical.

---

### BRANCH E: DES Installer Templates Not Included

**WHY 1E**: The plugin does not include DES installer templates (`.pre-commit-config-nwave.yaml`, `.des-audit-README.md`).
**Evidence**: `build_plugin.py` `copy_templates()` (lines 602-620) only copies `step-tdd-cycle-schema.json` and `roadmap-schema.json` (runtime templates). The `des_plugin.py` `DES_TEMPLATES` (lines 29-33) includes `.pre-commit-config-nwave.yaml` and `.des-audit-README.md` (setup templates). The plugin correctly excludes these because they are installer-time artifacts, not runtime artifacts.

**ROOT CAUSE E**: By design -- these are installer-time templates not needed at runtime. **Impact**: None for DES enforcement.

---

## Cross-Validation

| Root Cause | Explains "DES not installed"? | Severity |
|------------|------------------------------|----------|
| A: Missing Write/Edit guard hooks | YES -- DES enforcement is incomplete without these guards. Write/Edit operations during a DELIVER session would not be validated. | CRITICAL |
| B: Stale "Task" matcher | POSSIBLY -- if Claude Code no longer recognizes "Task" matcher, PreToolUse hooks would silently fail, making DES appear non-functional. | HIGH |
| C: No config bootstrap | PARTIALLY -- DES may appear broken if audit logging fails, but core enforcement should still work. | LOW |
| D: Missing utility scripts | NO -- these are not part of hook enforcement. | LOW |
| E: Missing installer templates | NO -- these are installer-time only. | NONE |

**Consistency check**: Root Causes A and B are the primary contributors. If the PreToolUse matcher "Task" is no longer recognized, the hook would never fire, making it appear as if DES is completely not installed. If it IS recognized but Write/Edit guards are missing, DES enforcement would be partial (Agent invocations checked, but file writes not guarded).

---

## Solution Recommendations

### P0: Immediate Mitigations

1. **Update `_HOOK_EVENTS` matcher from "Task" to "Agent"** in `build_plugin.py` line 502.
   - Change: `("PreToolUse", "Task", "pre-task")` -> `("PreToolUse", "Agent", "pre-task")`
   - Also: `("PostToolUse", "Task", "post-tool-use")` -> `("PostToolUse", "Agent", "post-tool-use")`
   - File: `scripts/build_plugin.py`, line 502-503

### P1: Permanent Fixes

2. **Add Write/Edit guard hooks to plugin build** -- Add two more PreToolUse entries to `_HOOK_EVENTS` or add a separate function in `build_plugin.py` that generates Write/Edit guard hooks adapted for `${CLAUDE_PLUGIN_ROOT}` paths.
   - The shell fast-path logic from `des_plugin.py` lines 567-605 needs to be adapted for plugin context (replace `$HOME/.claude/lib/python` with `${CLAUDE_PLUGIN_ROOT}/scripts`).
   - File: `scripts/build_plugin.py`, `generate_hook_config()` function

3. **Extract shared hook definition** -- Create a single source of truth for hook events that both `build_plugin.py` and `des_plugin.py` consume, preventing future divergence.

### P2: Prevention / Early Detection

4. **Add parity test** -- Create a test that compares the set of hook events generated by `build_plugin.py` against the set in `des_plugin.py`, failing CI if they diverge (accounting for expected differences in path format).
   - Location: `tests/build/acceptance/plugin/`

5. **Add SessionStart matcher alignment** -- Verify `des_plugin.py`'s SessionStart matcher ("startup") matches the plugin's ("startup"). Currently aligned, but should be covered by the parity test.

---

## Summary of Key Files and Line Numbers

| File | Lines | What |
|------|-------|------|
| `scripts/build_plugin.py` | 501-507 | `_HOOK_EVENTS` -- missing Write/Edit, stale "Task" matcher |
| `scripts/build_plugin.py` | 496-499 | `_HOOK_COMMAND_TEMPLATE` -- uses `${CLAUDE_PLUGIN_ROOT}` |
| `scripts/build_plugin.py` | 510-525 | `generate_hook_config()` -- generates hooks from `_HOOK_EVENTS` |
| `scripts/build_plugin.py` | 575-599 | `copy_des_module()` -- copies DES, rewrites imports |
| `scripts/build_plugin.py` | 602-620 | `copy_templates()` -- copies runtime templates |
| `scripts/build_plugin.py` | 623-654 | `generate_hooks_json()` -- writes hooks.json |
| `scripts/build_plugin.py` | 657-669 | `generate_hook_wrapper()` -- writes des-hook script |
| `scripts/install/plugins/des_plugin.py` | 23-26 | `DES_SCRIPTS` -- utility scripts (not in plugin) |
| `scripts/install/plugins/des_plugin.py` | 29-33 | `DES_TEMPLATES` -- installer templates (not in plugin) |
| `scripts/install/plugins/des_plugin.py` | 41-44 | `HOOK_COMMAND_TEMPLATE` -- uses `$HOME` paths |
| `scripts/install/plugins/des_plugin.py` | 47-53 | `HOOK_EVENTS` -- 5 event types |
| `scripts/install/plugins/des_plugin.py` | 556-605 | Write/Edit guard hook generation -- **NOT in plugin** |
| `.github/workflows/release-prod.yml` | 270-343 | build-plugin job -- builds, validates, packages ZIP |
| `docs/adrs/ADR-002-des-hooks-in-plugin.md` | 1-85 | Architecture decision for DES in plugin |
