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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"name": "autodev",
"description": "Autonomous development workflow skills for coding agents",
"version": "6.0.4",
"version": "6.0.5",
"source": "./",
"author": {
"name": "Jon Langevin",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "autodev",
"description": "Autonomous development workflow skills for coding agents: design, review, planning, execution, monitoring, and retrospectives",
"version": "6.0.4",
"version": "6.0.5",
"author": {
"name": "Jon Langevin",
"email": "jon@gocodealone.com"
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "autodev",
"displayName": "Autonomous Dev Kit",
"description": "Autonomous development workflow skills for coding agents",
"version": "6.0.4",
"version": "6.0.5",
"author": {
"name": "Jon Langevin",
"email": "jon@gocodealone.com"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Per-skill host-conditional audit: [tests/cross-llm-coverage.md](tests/cross-llm-

7. **alignment-check** - Activates after adversarial review of plan passes. Narrowly structural: every design requirement maps to a plan task; every plan task traces to a design requirement; the Scope Manifest is well-formed (forward + reverse + manifest trace via `tests/plan-scope-check.sh`).

8. **scope-lock** - Activates immediately after `alignment-check` PASS. Stamps the plan with `Locked <timestamp>`, computes the manifest's sha256 into `<plan>.scope-lock`, commits both. From this point until completion (or an explicit user-approved amendment), the task list, PR count, and feature scope are immutable. Design backports that do not change the manifest are allowed; manifest changes go through ADR + alignment. The lock hash covers only the `## Scope Manifest` block, so explanatory design/task notes can evolve without invalidating scope. `subagent-driven-development` re-checks the lock between tasks; `finishing-a-development-branch` re-checks before any PR is created.
8. **scope-lock** - Activates immediately after `alignment-check` PASS. Stamps the plan with `Locked <timestamp>`, computes the manifest's sha256 into `<plan>.scope-lock`, commits both. From this point until completion (or an explicit user-approved amendment), the task list, PR count, and feature scope are immutable. Design backports that do not change the manifest are allowed; manifest changes go through ADR + alignment. The lock hash covers only the `## Scope Manifest` block, so explanatory design/task notes can evolve without invalidating scope. `subagent-driven-development` re-checks the lock between tasks; `finishing-a-development-branch` re-checks before any PR is created. When the design is fully complete, `bash "${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-complete" <plan> --evidence "<verification>"` marks it `Complete`, removes the lock file, and prunes session reminder traces.

9. **subagent-driven-development** or **executing-plans** - Activates with a locked plan. Dispatches fresh subagent per task with two-stage review (spec compliance, then code quality). Between tasks, re-runs the scope-lock check; on lock drift, stops the line and surfaces the discrepancy. Phase/task completions are logged in compressed JSONL to `.autodev/state/phase-progress.jsonl` when a locked plan continues.

Expand Down
17 changes: 17 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Autonomous Dev Kit Release Notes

## v6.0.5 (2026-05-26)

### Scope-lock completion cleanup

- Added `hooks/scope-lock-complete` so completed locked plans can be marked
`Complete <UTC>`, have their `.scope-lock` file removed, and prune session
lock/snapshot traces.
- Scoped strict prompt reminders to session-owned locked plans when the host
provides a transcript path, preventing unrelated historical locks from
attaching to new autonomous work.
- Changed PreCompact snapshots to include only active locked plans, reducing
oversized hook JSON in repositories with many old plan documents.
- Clarified that adversarial design/plan review should dispatch a subagent with
the full adversarial prompt whenever the host exposes subagent support.
- Added hook-contract regressions for completion cleanup, session-scoped prompt
reminders, and locked-only PreCompact snapshots.

## v6.0.4 (2026-05-26)

### Stop-hook feedback formatting
Expand Down
42 changes: 42 additions & 0 deletions decisions/0001-complete-scope-locks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 0001. Complete scope locks explicitly

**Status:** Accepted
**Date:** 2026-05-26
**Decision-makers:** Jon Langevin, Codex
**Related:** `hooks/scope-lock-complete`, `skills/scope-lock/SKILL.md`, `tests/hook-contracts.sh`

## Context

Scope locks prevent autonomous agents from silently changing a plan after
alignment. The previous lifecycle had a creation path but no completion path,
so old locked plans continued to trigger prompt, stop, and pre-compact
reminders in later unrelated sessions.

## Decision

We will complete locks explicitly with `hooks/scope-lock-complete`. The helper
verifies the lock when possible, changes the plan status to `Complete`, removes
the `.scope-lock` file, prunes session reminder traces, and records compact
completion evidence.

**Alternatives considered and rejected:**

- **Ignore old locks through active-context only** — hook state is repo-local
and must not depend on one workspace-specific state file.
- **Teach every hook to infer completion from PR history** — too expensive and
ambiguous; completion needs an explicit operator/agent action.

## Consequences

**Positive:**

- Completed designs no longer nag unrelated sessions.
- Lock cleanup has one testable command instead of manual state edits.

**Negative:**

- Agents must remember one more lifecycle command before claiming a full
locked design is complete.

**Reversibility:** Low cost. Revert the helper and tests; existing completed
plans remain ordinary markdown with no live lock file.
33 changes: 28 additions & 5 deletions hooks/completion-claim-guard
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,23 @@ plans_dir="${cwd_dir}/docs/plans"
[ -d "$plans_dir" ] || exit 0

find_locked_plans() {
if [ -n "$session_key" ]; then
workspace_locked_plans() {
find "$plans_dir" -maxdepth 1 -name '*.md' \
! -name '*-design.md' ! -name 'README.md' 2>/dev/null \
| grep -v '\.scope-lock' \
| sort \
| while IFS= read -r plan; do
[ -n "$plan" ] || continue
grep -q '\*\*Status:\*\* Locked' "$plan" 2>/dev/null || continue
printf '%s\n' "$plan"
done
}

session_locked_plans() {
local state_file="${cwd_dir}/.claude/autodev-state/session-locks.jsonl"
[ -f "$state_file" ] || return 0
jq -r --arg session "$session_key" \
'select(.ev == "session-lock" and .session == $session) | .pl' \
'select(.ev == "session-lock" and .session == $session) | .pl // empty' \
"$state_file" 2>/dev/null \
| awk 'NF && !seen[$0]++' \
| while IFS= read -r plan; do
Expand All @@ -78,11 +90,22 @@ find_locked_plans() {
grep -q '\*\*Status:\*\* Locked' "$resolved" 2>/dev/null || continue
printf '%s\n' "$resolved"
done
}

if [ -n "$session_key" ]; then
session_plans=$(session_locked_plans)
if [ -n "$session_plans" ]; then
printf '%s\n' "$session_plans"
return 0
fi
workspace_plans=$(workspace_locked_plans)
if [ "$(printf '%s\n' "$workspace_plans" | awk 'NF {count++} END {print count+0}')" -eq 1 ]; then
printf '%s\n' "$workspace_plans"
fi
return 0
fi

grep -rl '\*\*Status:\*\* Locked' "$plans_dir" 2>/dev/null \
| grep '\.md$' | grep -v '\.scope-lock' || true
workspace_locked_plans
}

locked_plans=$(find_locked_plans)
Expand Down Expand Up @@ -139,7 +162,7 @@ If this is only a phase/task completion:
- Continue automatically into the next phase/task from the Scope Manifest.

Only stop if one of these is true:
- The entire locked design is complete and verified.
- The entire locked design is complete and verified; run bash \"\${CLAUDE_PLUGIN_ROOT:-.}/hooks/scope-lock-complete\" <plan> --evidence \"<verification>\" to mark it complete and remove lock traces.
- There is a hard blocker that requires human input.
- The next action is a production deploy or destructive production change without already-recorded human approval.

Expand Down
58 changes: 53 additions & 5 deletions hooks/pre-compact-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,63 @@ hook_input=$(cat || true)

cwd_dir=$(printf '%s' "$hook_input" | jq -r '.cwd // empty' 2>/dev/null || true)
[ -z "$cwd_dir" ] && cwd_dir="${PWD}"
transcript_path=$(printf '%s' "$hook_input" | jq -r '.transcript_path // empty' 2>/dev/null || true)
session_key=""
[ -n "$transcript_path" ] && session_key=$(basename "$transcript_path")

# ── Find locked plans and collect their state ─────────────────────────────────
plans_dir="${cwd_dir}/docs/plans"
state_section=""

if [ -d "$plans_dir" ]; then
workspace_locked_plans() {
find "$plans_dir" -maxdepth 1 -name '*.md' \
! -name '*-design.md' ! -name 'README.md' 2>/dev/null \
| grep -v '\.scope-lock' \
| sort \
| while IFS= read -r plan; do
[ -n "$plan" ] || continue
grep -q '\*\*Status:\*\* Locked' "$plan" 2>/dev/null || continue
printf '%s\n' "$plan"
done
}

session_locked_plans() {
local state_file="${cwd_dir}/.claude/autodev-state/session-locks.jsonl"
[ -f "$state_file" ] || return 0
jq -r --arg session "$session_key" \
'select(.ev == "session-lock" and .session == $session) | .pl // empty' \
"$state_file" 2>/dev/null \
| awk 'NF && !seen[$0]++' \
| while IFS= read -r plan; do
[ -n "$plan" ] || continue
case "$plan" in
/*) resolved="$plan" ;;
*) resolved="${cwd_dir}/${plan}" ;;
esac
[ -f "$resolved" ] || continue
grep -q '\*\*Status:\*\* Locked' "$resolved" 2>/dev/null || continue
printf '%s\n' "$resolved"
done
}

locked_plan_stream() {
if [ -n "$session_key" ]; then
session_plans=$(session_locked_plans)
if [ -n "$session_plans" ]; then
printf '%s\n' "$session_plans"
return 0
fi
workspace_plans=$(workspace_locked_plans)
if [ "$(printf '%s\n' "$workspace_plans" | awk 'NF {count++} END {print count+0}')" -eq 1 ]; then
printf '%s\n' "$workspace_plans"
fi
return 0
fi

workspace_locked_plans
}

while IFS= read -r plan; do
[ -z "$plan" ] && continue
plan_name=$(basename "$plan")
Expand Down Expand Up @@ -61,21 +112,18 @@ if [ -d "$plans_dir" ]; then
pending_entries="${pending_entries:-}${entry}
"
fi
done < <(find "$plans_dir" -maxdepth 1 -name '*.md' \
! -name '*-design.md' ! -name 'README.md' 2>/dev/null \
| grep -v '\.scope-lock' | sort || true)
done < <(locked_plan_stream)
fi

if [ -z "$state_section" ]; then
# No plans found — nothing to snapshot. Exit silently.
# No active locked plans found — nothing to snapshot. Exit silently.
exit 0
fi

# ── Append to autodev-state file ─────────────────────────────────────────
STATE_DIR="${cwd_dir}/.claude/autodev-state"
mkdir -p "$STATE_DIR" 2>/dev/null || true
STATE_FILE="${STATE_DIR}/in-progress.jsonl"
LOCK_FILE="${STATE_DIR}/.in-progress.lock"

ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

Expand Down
49 changes: 47 additions & 2 deletions hooks/prompt-strict-interpretation
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ prompt=$(printf '%s' "$hook_input" | jq -r '.prompt // empty' 2>/dev/null || tru

cwd_dir=$(printf '%s' "$hook_input" | jq -r '.cwd // empty' 2>/dev/null || true)
[ -z "$cwd_dir" ] && cwd_dir="${PWD}"
transcript_path=$(printf '%s' "$hook_input" | jq -r '.transcript_path // empty' 2>/dev/null || true)
session_key=""
[ -n "$transcript_path" ] && session_key=$(basename "$transcript_path")

# ── Check for trigger phrases ────────────────────────────────────────────────
# These are phrases that agents have used as license to rescope on a locked plan.
Expand Down Expand Up @@ -92,8 +95,50 @@ locked_plan=""
locked_plan_name=""

if [ -d "$plans_dir" ]; then
locked_plan=$(grep -rl '\*\*Status:\*\* Locked' "$plans_dir" 2>/dev/null \
| grep '\.md$' | grep -v '\.scope-lock' | head -1 || true)
workspace_locked_plans() {
find "$plans_dir" -maxdepth 1 -name '*.md' \
! -name '*-design.md' ! -name 'README.md' 2>/dev/null \
| grep -v '\.scope-lock' \
| sort \
| while IFS= read -r plan; do
[ -n "$plan" ] || continue
grep -q '\*\*Status:\*\* Locked' "$plan" 2>/dev/null || continue
printf '%s\n' "$plan"
done
}

session_locked_plans() {
local state_file="${cwd_dir}/.claude/autodev-state/session-locks.jsonl"
[ -f "$state_file" ] || return 0
jq -r --arg session "$session_key" \
'select(.ev == "session-lock" and .session == $session) | .pl // empty' \
"$state_file" 2>/dev/null \
| awk 'NF && !seen[$0]++' \
| while IFS= read -r plan; do
[ -n "$plan" ] || continue
case "$plan" in
/*) resolved="$plan" ;;
*) resolved="${cwd_dir}/${plan}" ;;
esac
[ -f "$resolved" ] || continue
grep -q '\*\*Status:\*\* Locked' "$resolved" 2>/dev/null || continue
printf '%s\n' "$resolved"
done
}

if [ -n "$session_key" ]; then
session_plans=$(session_locked_plans)
if [ -n "$session_plans" ]; then
locked_plan=$(printf '%s\n' "$session_plans" | head -1)
else
workspace_plans=$(workspace_locked_plans)
if [ "$(printf '%s\n' "$workspace_plans" | awk 'NF {count++} END {print count+0}')" -eq 1 ]; then
locked_plan=$(printf '%s\n' "$workspace_plans" | head -1)
fi
fi
else
locked_plan=$(workspace_locked_plans | head -1 || true)
fi
fi

# No locked plan — the invariant only applies under a lock.
Expand Down
Loading
Loading