Skip to content
Merged
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
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,34 @@ Per-feature specs live at `specs/<feature-id>/` in this repo; archived at `specs

`<feature-id>` format: `^[A-Z]+-\d+(-[a-z0-9-]+)?$` (e.g., `AI-001-ollama-public`) or `^\d{4}-\d{2}-\d{2}-[a-z0-9-]+$` (e.g., `2026-05-13-cleanup`).

### Discipline Gate (NON-NEGOTIABLE)

Before creating ANY branch for code changes in this repo, evaluate against `pattern-spec-driven-development.md` "Trigger Criteria". SDD is mandatory if ANY of these apply:

- Change produces ~50–300 LOC of production diff (excluding tests, generated files, lockfiles)
- Change touches a public contract (API, CLI flag, exported type, alias name, file path, deployed config schema)
- Change adds or removes a dependency
- Change is the first step of a multi-PR sequence
- The change warrants a Socratic Guardrail pause (architectural decisions, schema design, concurrency, breaking changes)

**If trigger met, follow this order — no shortcuts:**

1. Add vault entry to `10_projects/<repo>/11-tasks.md` (the "vault gate")
2. Run `init-spec.{sh,ps1} <feature-id>` to scaffold `specs/<feature-id>/` (the script enforces the vault-gate check; bypass only with `-ForceNoVault` + explicit user-facing justification)
3. Fill `proposal.md` (why + what + acceptance criteria) **before** writing implementation code
4. Fill `tasks.md` in TDD order
5. Implement; tick boxes as you go
6. Fill `verification.md` with evidence (commit hashes, test outputs, smoke results)
7. On merge: move folder to `specs/archive/<feature-id>/` and tick the vault entry with the PR link

**Banned phrases when planning work in this session:**

- "I'll do vault hygiene later"
- "Will add the spec entry after merge"
- "Let me commit first and document later"

Standing Order #3 (vault hygiene) is **in-session, not 'later'**. Every "later" is debt that compounds and historically gets forgotten between sessions. If a vault hygiene action genuinely cannot fit in the current turn, create an explicit tracked task for the debt — never leave it as a verbal promise.

## Response Protocol

1. **Classify Task:** Determine if Low Load (Execute) or High Load (Mentor).
Expand Down
20 changes: 13 additions & 7 deletions scripts/claude-session-start.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ if (-not $CWD) {
}

$KnowledgeVault = Join-Path $env:USERPROFILE 'Projects\knowledge'
$ContextLines = ''
# SDD discipline reminder -- unconditional, first in additionalContext (SDD-001).
# Surfaces the Discipline Gate to every Claude Code session regardless of CWD,
# repo, or vault state. Cross-OS parity with claude-session-start.sh. All
# subsequent diagnostic blocks (claude-mem, doctor, hive, specs, vault, memory)
# APPEND to $ContextLines defensively so they cannot wipe this reminder.
$ContextLines = '[sdd] Before your first tool call, read `AGENTS.md` at the repo root (or `~/Projects/dotfiles/AGENTS.md` as fallback) and apply its "Spec-Driven Development" (including the Discipline Gate) and "Standing Orders" sections. SDD applies by default for PR-sized changes (~50-300 LOC, public contract, new dep, multi-PR sequence). Skip ONLY for: typos, comment-only edits, mechanical refactors, bug fixes <20 lines with obvious cause, documentation-only changes. When in doubt, ASK the user.'

# --- Self-heal claude-mem plugin if marketplace shipped broken artifacts ---
# Patches .mcp.json (${_R%/} regression, upstream #2385) and installs the
Expand All @@ -45,7 +50,8 @@ if (Test-Path $ClaudeMemHeal) {
$healOutput = & pwsh -NoProfile -File $ClaudeMemHeal 2>&1 | Out-String
$healOutput = $healOutput.Trim()
if ($healOutput) {
$ContextLines = "[claude-mem] self-healed plugin install:`n$healOutput"
$healBlock = "[claude-mem] self-healed plugin install:`n$healOutput"
$ContextLines = "$ContextLines`n`n$healBlock"
}
} catch {
# Non-fatal -- session continues even if heal fails
Expand Down Expand Up @@ -203,14 +209,14 @@ function Find-VaultRoot {

$VaultRoot = Find-VaultRoot -Path $CWD

if (-not $VaultRoot -and -not $ContextLines) {
# Not inside a vault and no project detected - exit cleanly
exit 0
}
# Note: previous versions exited silently here when both $VaultRoot and
# $ContextLines were empty. After SDD-001 the [sdd] reminder is always present,
# so $ContextLines is never empty -- the early-exit branch is now dead code and
# was removed. The hook always emits the additionalContext envelope.

if ($VaultRoot) {
$VaultName = Split-Path -Leaf $VaultRoot
$ContextLines = "Obsidian vault detected: $VaultName ($VaultRoot)" + $ContextLines
$ContextLines = "Obsidian vault detected: $VaultName ($VaultRoot)`n`n" + $ContextLines
}

# --- Knowledge maintenance health check ---
Expand Down
21 changes: 15 additions & 6 deletions scripts/claude-session-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,23 @@ if [ -z "$CWD" ]; then
CWD="$(pwd)"
fi

# SDD discipline reminder -- unconditional, first in additionalContext (SDD-001).
# Surfaces the Discipline Gate to every Claude Code session regardless of CWD,
# repo, or vault state. Cross-OS parity with claude-session-start.ps1. All
# subsequent diagnostic blocks (claude-mem, doctor, hive, specs, vault, memory)
# APPEND to CONTEXT_LINES defensively so they cannot wipe this reminder.
CONTEXT_LINES='[sdd] Before your first tool call, read `AGENTS.md` at the repo root (or `~/Projects/dotfiles/AGENTS.md` as fallback) and apply its "Spec-Driven Development" (including the Discipline Gate) and "Standing Orders" sections. SDD applies by default for PR-sized changes (~50-300 LOC, public contract, new dep, multi-PR sequence). Skip ONLY for: typos, comment-only edits, mechanical refactors, bug fixes <20 lines with obvious cause, documentation-only changes. When in doubt, ASK the user.'

# --- Self-heal claude-mem plugin if marketplace shipped broken artifacts ---
# Patches .mcp.json (${_R%/} regression, upstream #2385) and installs the
# missing zod runtime dep. Silent on healthy installs.
CLAUDE_MEM_HEAL="$SCRIPT_DIR/claude-mem-heal.sh"
CONTEXT_LINES=""
if [ -x "$CLAUDE_MEM_HEAL" ]; then
HEAL_OUTPUT=$(bash "$CLAUDE_MEM_HEAL" 2>&1) || true
if [ -n "$HEAL_OUTPUT" ]; then
CONTEXT_LINES="[claude-mem] self-healed plugin install:
CONTEXT_LINES="$CONTEXT_LINES

[claude-mem] self-healed plugin install:
$HEAL_OUTPUT"
fi
fi
Expand Down Expand Up @@ -187,14 +195,15 @@ if [ -d "$CWD/.git" ]; then
detect_repo_specs
fi

if [ -z "$VAULT_ROOT" ] && [ -z "$CONTEXT_LINES" ]; then
# Not inside a vault and no project detected — exit cleanly
exit 0
fi
# Note: previous versions exited silently here when both VAULT_ROOT and
# CONTEXT_LINES were empty. After SDD-001 the [sdd] reminder is always present,
# so CONTEXT_LINES is never empty -- the early-exit branch is now dead code and
# was removed. The hook always emits the additionalContext envelope.

if [ -n "$VAULT_ROOT" ]; then
VAULT_NAME=$(basename "$VAULT_ROOT")
CONTEXT_LINES="Obsidian vault detected: $VAULT_NAME ($VAULT_ROOT)

$CONTEXT_LINES"
fi

Expand Down
60 changes: 60 additions & 0 deletions specs/SDD-001-discipline-gate/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
id: "SDD-001-discipline-gate"
type: spec
status: draft
created: "2026-05-18"
tags: [spec, proposal]
template_version: "1.0"
---

# SDD-001-discipline-gate

> Scope of THIS PR (PR A in the SDD-001 series): Tier 1 (`AGENTS.md` rule block) + Tier 2 (SessionStart hook injects deterministic SDD reminder in `additionalContext`). Tiers 3-5 (settings.json portability, CI spec-gate, PR template) ship as separate atomic PRs (SDD-002, SDD-003).

## Why

Session audit on 2026-05-18 found that two recent atomic PRs in this repo (BUG-002 / PR #47, BUG-003 / PR #48) bypassed the SDD discipline defined in `pattern-spec-driven-development.md`: no vault entry first, no `init-spec` scaffold, no `specs/<id>/` folder — despite both PRs meeting trigger criteria (>50 LOC, public contract, new dep, multiple Socratic Guardrail pauses). The discipline failed silently because `git checkout -b` does not enforce the vault gate, and the SessionStart hook only *reports* repo specs status rather than nudging the agent to *apply* SDD. This PR closes the first two enforcement gaps: documents the rule explicitly in the canonical SSOT (`AGENTS.md`) and surfaces it every session via the hook.

## What

Two observable behavior changes after this PR:

1. **`AGENTS.md` at repo root contains a new `## SDD Discipline Gate (NON-NEGOTIABLE)` section** that codifies: the trigger criteria, the mandatory order (vault entry → `init-spec.{sh,ps1}` → proposal → tasks → code → verification), the skip criteria (typos / comment-only / mechanical refactor / bug <20 LOC / doc-only), and a banned-phrases list for vault-hygiene "later" promises that historically compound into debt.

2. **`scripts/claude-session-start.ps1` and `scripts/claude-session-start.sh` inject a deterministic, non-conditional reminder line at the *start* of `additionalContext`** referencing the new `AGENTS.md` section. The reminder fires every session regardless of CWD, repo state, or vault presence (existing `Test-RepoSpecs` / `find_hive_project` blocks remain as diagnostics aside). Cross-OS parity locked in bats.

## Out of scope

- **Tier 3** — tracking `~/.claude/settings.json` in dotfiles + deep-merge install logic. Significant standalone scope (~80 LOC + JSON merge complexity). Ships as `SDD-002-settings-portability`.
- **Tier 4** — `.github/workflows/ci.yml` `spec-gate` job. Ships as part of `SDD-003-ci-gate`.
- **Tier 5** — `.github/PULL_REQUEST_TEMPLATE.md`. Ships with Tier 4 in `SDD-003-ci-gate`.
- Removing or renaming existing hook diagnostics (`Test-RepoSpecs`, claude-mem heal, doctor drift) — they coexist with the new reminder.
- Modifying the canonical pattern `pattern-spec-driven-development.md` in the vault — the pattern already defines the rule; this PR enforces it in the agent's context.

## Risks / open questions

- **Risk: hook output grows and consumers truncate `additionalContext`.** The reminder adds ~5 lines. Existing `additionalContext` payload is variable but already includes claude-mem heal output, doctor drift, vault detection, specs detection. Mitigation: keep the new reminder under 4 lines, prefix with `[sdd]` so it's filterable like existing `[claude-mem]`, `[doctor]`, `[specs]` prefixes.
- **Risk: AGENTS.md grows past the ≤70-line pointer-style target.** Current AGENTS.md is denser than the pointer files (it's the SSOT, not a pointer). Mitigation: keep the new section terse (≤30 lines), link to the pattern doc for full rationale, do not duplicate the full pattern body.
- **Risk: future agents ignore the rule (no enforcement, just documentation).** True for Tier 1+2 alone. Tier 4 (CI spec-gate) is the hard enforcement layer and ships separately. Tier 1+2 are the soft layer that surfaces the rule at the right moment; CI is the safety net.
- **Open question: should the reminder text reference the AGENTS.md section by anchor link (`#sdd-discipline-gate-non-negotiable`) for click-through in the agent's rendering?** GitHub-flavored anchor would be `#sdd-discipline-gate-non-negotiable`. Decision: include the anchor, fall back to section title text if anchor not supported.

## Acceptance criteria

- [ ] `AGENTS.md` at repo root contains an H2 section titled exactly `## SDD Discipline Gate (NON-NEGOTIABLE)`
- [ ] The new section enumerates: (a) trigger criteria, (b) mandatory ordered process (vault entry → init-spec → proposal → tasks → code → verification), (c) skip criteria, (d) banned-phrases list
- [ ] `scripts/claude-session-start.ps1` writes a line containing literal text `Before your first tool call, read AGENTS.md` to `additionalContext`, unconditionally (no `if` gates around it)
- [ ] `scripts/claude-session-start.sh` writes the same reminder text, also unconditionally, in matching position
- [ ] Local test on the user's machine: `echo '{"cwd":"<any path>","session_id":"test"}' | pwsh -NoProfile -File scripts/claude-session-start.ps1` produces JSON with the SDD reminder present in `hookSpecificOutput.additionalContext`
- [ ] Same for `.sh` via `echo '{"cwd":"...","session_id":"test"}' | bash scripts/claude-session-start.sh`
- [ ] Bats parity assert: both hook scripts contain the SDD reminder marker
- [ ] PSScriptAnalyzer clean on the modified `.ps1`
- [ ] `bash -n` clean on the modified `.sh`; shellcheck clean
- [ ] No regressions: existing hook behavior (vault detection, claude-mem heal, doctor drift, specs detection) all still fires when conditions match

## References

- Vault: `10_projects/dotfiles/11-tasks.md` "SDD-001-discipline-gate" backlog entry (added 2026-05-18 in same session as audit)
- Pattern: `00_meta/patterns/pattern-spec-driven-development.md` (the SSOT this PR enforces)
- Sibling PRs: `SDD-002-settings-portability` (next), `SDD-003-ci-gate` (after)
- Triggered by: BUG-002 / PR #47 + BUG-003 / PR #48 audit (this session, 2026-05-18)
- Related lessons: `10_projects/dotfiles/90-lessons.md` 2026-05-18 entries
58 changes: 58 additions & 0 deletions specs/SDD-001-discipline-gate/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
tags: [spec, tasks]
created: "2026-05-18"
---

# Tasks - SDD-001-discipline-gate

> TDD order. Scope of THIS PR = Tier 1 (AGENTS.md rule) + Tier 2 (hook nudge). Tiers 3-5 ship in sibling specs SDD-002 / SDD-003. One spec equals one atomic PR per `pattern-spec-driven-development.md`.

## Setup

- [x] Vault entry exists in `10_projects/dotfiles/11-tasks.md` (added 2026-05-18 same session; pending commit by user in vault repo)
- [x] Branch created from main: `feat/SDD-001-discipline-gate`
- [x] Spec scaffolded via `scripts/init-spec.ps1 SDD-001-discipline-gate` (vault gate passed)
- [x] `proposal.md` complete; acceptance criteria are testable
- [ ] Resolve anchor question in `proposal.md` "Risks / open questions" before locking the hook reminder text (decision: include `#sdd-discipline-gate-non-negotiable` anchor)

## Implementation (TDD)

### AGENTS.md rule (Tier 1)

- [ ] Write failing bats test `tests/agents-md.bats` (new file) asserting: AGENTS.md exists, contains H2 `## SDD Discipline Gate (NON-NEGOTIABLE)`, enumerates trigger criteria + skip criteria + banned-phrases block
- [ ] Edit `AGENTS.md` to add the section. Keep ≤30 lines. Link to `[[pattern-spec-driven-development]]` for full rationale, do not duplicate the pattern body
- [ ] Verify bats green (manual grep simulation while bats not local)

### Hook reminder Windows (Tier 2 .ps1)

- [ ] Add failing bats assert in `tests/hooks.bats` (new file): `.ps1` contains literal SDD reminder text + `[sdd]` prefix marker
- [ ] Edit `scripts/claude-session-start.ps1` to inject the reminder at the START of `$ContextLines` (right after line 37 `$ContextLines = ''`, before claude-mem heal block at line 42). Non-conditional — runs every session regardless of CWD / vault / repo state. Use `[sdd]` prefix to match `[claude-mem]` / `[doctor]` / `[specs]` convention
- [ ] Local smoke: `echo '{"cwd":"C:\\test","session_id":"test"}' | pwsh -NoProfile -File scripts/claude-session-start.ps1` -- output JSON's `hookSpecificOutput.additionalContext` STARTS with the SDD reminder
- [ ] PSScriptAnalyzer clean on the modified `.ps1`

### Hook reminder Linux (Tier 2 .sh)

- [ ] Add parity assert in `tests/hooks.bats`: `.sh` contains same SDD reminder text, same `[sdd]` prefix
- [ ] Edit `scripts/claude-session-start.sh` to inject reminder in matching position with identical text content (cross-OS parity)
- [ ] Local smoke: `echo '{"cwd":"/test","session_id":"test"}' | bash scripts/claude-session-start.sh` -- output JSON's `additionalContext` includes the SDD reminder
- [ ] `bash -n` syntax clean; `shellcheck` clean (severity error+warning)

### Cross-OS parity locks

- [ ] Bats assert: SDD reminder text is byte-identical between `.ps1` and `.sh` (no drift class like the verify-string bugs we just fixed)
- [ ] Bats assert: prefix `[sdd]` present in both
- [ ] Existing hook diagnostics (`Test-RepoSpecs`, `find_hive_project`/`Find-HiveProject`, claude-mem heal, doctor drift, vault integrity, knowledge health) still fire when their respective conditions match -- no regressions

## Closing

- [ ] Every acceptance criterion from `proposal.md` covered by ≥1 test
- [ ] PSScriptAnalyzer (Error+Warning) clean
- [ ] `bash -n` + `shellcheck` (severity error) clean
- [ ] No unrelated changes in the diff (no scope creep into Tier 3/4/5)
- [ ] `verification.md` filled with: smoke command outputs (both .ps1 and .sh), bats simulation results, PSScriptAnalyzer + shellcheck reports, before/after snippets
- [ ] PR opened referencing this spec folder; PR body notes scope split (Tier 1+2 here; Tier 3 in SDD-002; Tier 4+5 in SDD-003)
- [ ] Spec status moved `draft` → `implementing` when first code commit lands; → `verifying` when smoke green; → `archived` (move folder to `specs/archive/`) only after PR merge per archive policy in `pattern-spec-driven-development.md`

## Machine-readable features

`features.json` not required for this PR (no harness verification automation yet in this repo). Future SDD specs may opt in once the harness pattern matures. Acceptance criteria are verified by bats + manual smoke output captured in `verification.md`.
Loading
Loading