Claude
Skills
Sign in
Back

decision-journal

Included with Lifetime
$97 forever

Extracts and structures development decisions from diffs, manages decision journal entries, and detects human gate triggers. Use when logging decisions during gh-start, gh-commit, or gh-address. Use when summarizing decisions for PR bodies or when checking for gate-triggering changes like new dependencies, security modifications, or scope deviations.

Security

What this skill does


# Decision Journal

Captures, structures, and persists significant decisions made during AI-driven workflow execution. Uses post-hoc extraction from diffs rather than AI self-reporting during execution.

## Purpose

In a fully AI-driven workflow, decisions happen invisibly. This skill makes them visible by:
- Extracting decisions from completed diffs (what changed vs. what was expected)
- Detecting high-stakes changes that warrant human approval (gates)
- Structuring decisions into a machine-parseable journal format
- Condensing journals into PR-body-ready summaries

## Mode Routing

This skill operates in one of three modes. The calling command specifies the mode in its invocation prompt.

| Mode | When Used | What It Does |
|------|-----------|-------------|
| `init` | gh-start after branch creation | Creates journal file header |
| `log` | gh-start, gh-commit, gh-address after changes | Extracts decisions from diff + evaluates gate triggers |
| `summarize` | gh-pr during PR content generation | Condenses journal for PR body |

Read the mode from the invocation prompt and execute only that mode's instructions. If the mode is not one of `init`, `log`, or `summarize`, return an error: `"Unknown mode: {mode}. Expected one of: init, log, summarize."`

## Mode: init

**Input** (from invocation prompt): Issue number, branch name, issue title, issue body.

**Process:**

### Step 1: Read Configuration

```bash
# Read journal directory from settings (local > project > user > default)
JOURNAL_DIR=$(jq -r '.journal.dir // empty' .claude/settings.gh-workflow.local.json 2>/dev/null)
[ -z "$JOURNAL_DIR" ] && JOURNAL_DIR=$(jq -r '.journal.dir // empty' .claude/settings.gh-workflow.json 2>/dev/null)
[ -z "$JOURNAL_DIR" ] && JOURNAL_DIR=$(jq -r '.journal.dir // empty' "$HOME/.claude/settings.gh-workflow.json" 2>/dev/null)
[ -z "$JOURNAL_DIR" ] && JOURNAL_DIR=".decisions"
echo "Journal directory: $JOURNAL_DIR"
```

### Step 2: Generate Header

Generate the journal file header:

```markdown
# Decision Journal: Issue #{N} — {issue title}

**Issue**: #{N}
**Branch**: {branch-name}
**Started**: {YYYY-MM-DD}

---
```

**Output:** Return the header markdown and the resolved journal directory (from `.journal.dir` in settings, default `.decisions`). The calling command writes it to `{journal-dir}/issue-{N}.md`.

## Mode: log

**Input** (from invocation prompt): Description of what phase just completed (e.g., "task breakdown", "staged changes for commit", "addressed review feedback"). The calling command provides the relevant context.

**Process:**

### Step 1: Gather Context

```bash
# Get current branch and issue number
BRANCH=$(git branch --show-current)
ISSUE_NUM=$(echo "$BRANCH" | grep -oE 'issue-[0-9]+' | grep -oE '[0-9]+')

# Get the default branch
DEFAULT_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
[ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH=$(git rev-parse --verify origin/main >/dev/null 2>&1 && echo "main" || echo "master")
```

```bash
# Read all comprehension config from settings (local > project > user > default)
# Single merge for both journal and gates — used in Steps 1-3
GHW_CONFIG=$(jq -n '
  def defaults: {
    gates: {
      newDependencies:"on", securityChanges:"on", schemaChanges:"on",
      apiSurfaceChanges:"on", scopeDeviations:"on", ambiguousRequirements:"on",
      customTriggers:[], customTriggersMode:"on"
    },
    journal: { dir:".decisions", sensitivityDefault:"public" }
  };
  defaults
    * (try input catch {})
    * (try input catch {})
    * (try input catch {})
' "$HOME/.claude/settings.gh-workflow.json" \
  ".claude/settings.gh-workflow.json" \
  ".claude/settings.gh-workflow.local.json" 2>/dev/null)

JOURNAL_DIR=$(echo "$GHW_CONFIG" | jq -r '.journal.dir')
SENSITIVITY_DEFAULT=$(echo "$GHW_CONFIG" | jq -r '.journal.sensitivityDefault')
echo "Journal directory: $JOURNAL_DIR"
echo "Default sensitivity: $SENSITIVITY_DEFAULT"
```

Use `$JOURNAL_DIR` instead of `.decisions` for all journal file paths in this mode. Use `$SENSITIVITY_DEFAULT` as the default sensitivity for new entries.

```bash
# Get the diff to analyze (staged changes for commit, or branch diff for other phases)
git diff --stat "$DEFAULT_BRANCH"...HEAD
git diff --name-only "$DEFAULT_BRANCH"...HEAD
```

```bash
# Get issue context
gh issue view "$ISSUE_NUM" --json title,body,labels 2>/dev/null
```

### Step 2: Extract Decisions (Post-Hoc)

Analyze the completed diff against the issue context. Identify decisions by comparing:
- **What changed** vs. **what the issue requested** — scope decisions, requirement interpretations
- **Patterns chosen** vs. **alternatives available** — architecture, implementation trade-offs
- **Files touched** vs. **expected impact area** — scope deviations

For each significant decision found, generate a journal entry:

```markdown
### {YYYY-MM-DD HH:MM} [{CATEGORY}] {Decision Title}

**Command**: {gh-start | gh-commit | gh-address}
**Decision**: {What was decided}
**Alternatives**: {What else was considered, or "N/A" for obvious choices}
**Rationale**: {Why this choice was made}
**Risk**: {Low | Medium | High | Critical}
**Sensitivity**: {public | internal}
**Gate**: {No — AI decision}
**References**: {#M, #K — cross-issue refs, or "None"}

---
```

**Categories**: `architecture`, `requirements`, `trade-off`, `implementation`, `risk`, `scope`

**Sensitivity rules:**
- Default: Use the `$SENSITIVITY_DEFAULT` value from Step 1 config (falls back to `public` if not configured)
- Use `internal` for decisions involving: security rationale, credential/secret handling, vulnerability remediation, access control logic
- Never document specific vulnerability details, exploitation vectors, previous insecure states, or secret values/locations — even in `internal` entries

### Step 3: Evaluate Gate Triggers

Extract gate configuration from the `$GHW_CONFIG` loaded in Step 1:

```bash
# Extract gates from the config already loaded in Step 1
echo "$GHW_CONFIG" | jq '.gates'
```

If no configuration found, all gates default to `on`.

Check the diff against these gate detection heuristics:

| Trigger | Detection Method | Config Key |
|---------|-----------------|------------|
| New dependency | New entries in `package.json`, `requirements.txt`, `Gemfile`, `go.mod`, `Cargo.toml`, or new git submodule | `.gates.newDependencies` |
| Security changes | Files matching `*auth*`, `*security*`, `*permission*`, `*token*`, `*secret*`, `*crypto*`, `*session*`; changes to `.env*`, CORS/TLS config | `.gates.securityChanges` |
| Schema changes | Database migration files, changes to `schema.*`, `*model*` definitions, API type definitions | `.gates.schemaChanges` |
| API surface changes | New route/endpoint definitions, changed function signatures in public modules, new command/skill files | `.gates.apiSurfaceChanges` |
| Scope deviations | Files modified outside the expected impact area from the issue | `.gates.scopeDeviations` |
| Ambiguous requirements | Acceptance criteria containing vague terms ("should be fast", "user-friendly", "appropriate"), contradictory criteria | `.gates.ambiguousRequirements` |

```bash
# Example detection for new dependencies
git diff "$DEFAULT_BRANCH"...HEAD --name-only | grep -E "(package\.json|requirements\.txt|Gemfile|go\.mod|Cargo\.toml|\.gitmodules)" 2>/dev/null
```

```bash
# Example detection for security-related files
git diff "$DEFAULT_BRANCH"...HEAD --name-only | grep -iE "(auth|security|permission|token|secret|crypto|session|\.env)" 2>/dev/null
```

```bash
# Evaluate custom trigger patterns (glob patterns from config)
CUSTOM_TRIGGERS=$(echo "$GHW_CONFIG" | jq -r '.gates.customTriggers[]' 2>/dev/null)
if [ -n "$CUSTOM_TRIGGERS" ]; then
  CHANGED_FILES=$(git diff "$DEFAULT_BRANCH"...HEAD --name-only)
  echo "$CUSTOM_TRIGGERS" | while read -r pattern; do
    echo "$CHANGED_FILES" | grep -E "$pattern" 2>/dev/null
  done
fi
```

Custom trigger matches use the `.gates.customTriggersMode` conf

Related in Security