Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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: 2 additions & 0 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@

**Dependencies**: `blocked-by:t001`, `blocks:t002`, `t001.1` (subtask)

**GitHub issue sync**: Issue titles MUST be prefixed with `t{NNN}: `. TODO.md tasks MUST include `ref:GH#{issue}`. Always maintain both directions. See `workflows/plans.md` "GitHub Issue Sync" section.

Check notice on line 105 in .agents/AGENTS.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.agents/AGENTS.md#L105

Spaces inside code span elements
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

**After ANY TODO/planning edit**: Commit and push immediately. Planning-only files (TODO.md, todo/) go directly to main — no branch, no PR. Mixed changes (planning + non-exception files) use a worktree. NEVER `git checkout -b` in the main repo. See `workflows/plans.md` "Commit and Push After TODO Changes" section.

**Full docs**: `workflows/plans.md`, `tools/task-management/beads.md`
Expand Down
14 changes: 13 additions & 1 deletion .agents/scripts/commands/log-issue-aidevops.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,23 @@
4. Cancel
```

### Step 5b: Assign Task ID and Sync

Before creating the issue, assign a TODO.md task ID:

1. Read TODO.md to find the highest existing t-number
2. Assign the next sequential t-number (e.g., if highest is t148, use t149)
3. Prefix the issue title with `t{NNN}: `

Check notice on line 138 in .agents/scripts/commands/log-issue-aidevops.md

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.agents/scripts/commands/log-issue-aidevops.md#L138

Spaces inside code span elements
4. After issue creation, add the task to TODO.md with `ref:GH#{issue_number}`
5. Commit and push TODO.md

This ensures bidirectional sync between GitHub issues and TODO.md. See `workflows/plans.md` "GitHub Issue Sync" section.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

### Step 6: Create the Issue

```bash
gh issue create -R marcusquinn/aidevops \
--title "TITLE" \
--title "t{NNN}: TITLE" \
--body "$(cat <<'EOF'
BODY_CONTENT
EOF
Expand Down
224 changes: 224 additions & 0 deletions .agents/scripts/session-checkpoint-helper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env bash
# session-checkpoint-helper.sh - Persist session state to survive context compaction
# Part of aidevops framework: https://aidevops.sh
#
# Usage:
# session-checkpoint-helper.sh [command] [options]
#
# Commands:
# save Save current session checkpoint
# load Load and display current checkpoint
# clear Remove checkpoint file
# status Show checkpoint age and summary
# help Show this help
#
# Options:
# --task <id> Current task ID (e.g., t135.9)
# --next <ids> Comma-separated next task IDs
# --worktree <path> Active worktree path
# --branch <name> Active branch name
# --batch <name> Batch/PR name
# --note <text> Free-form context note
# --elapsed <mins> Minutes elapsed in session
# --target <mins> Target session duration in minutes
#
# The checkpoint file is written to:
# ~/.aidevops/.agent-workspace/tmp/session-checkpoint.md
#
# Design: AI agent writes checkpoint after each task completion and reads it
# before starting the next task. Survives context compaction because state
# is on disk, not in context window.

set -euo pipefail

readonly CHECKPOINT_DIR="${HOME}/.aidevops/.agent-workspace/tmp"
readonly CHECKPOINT_FILE="${CHECKPOINT_DIR}/session-checkpoint.md"

readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly RED='\033[0;31m'
readonly BOLD='\033[1m'
readonly DIM='\033[2m'
readonly NC='\033[0m'

print_success() { printf "${GREEN}%s${NC}\n" "$1"; }
print_warning() { printf "${YELLOW}%s${NC}\n" "$1"; }
print_error() { printf "${RED}%s${NC}\n" "$1" >&2; }
print_info() { printf "${DIM}%s${NC}\n" "$1"; }

ensure_dir() {
if [[ ! -d "$CHECKPOINT_DIR" ]]; then
mkdir -p "$CHECKPOINT_DIR"
fi
return 0
}

cmd_save() {
local current_task=""
local next_tasks=""
local worktree_path=""
local branch_name=""
local batch_name=""
local note=""
local elapsed_mins=""
local target_mins=""

while [[ $# -gt 0 ]]; do
case "$1" in
--task) current_task="$2"; shift 2 ;;
--next) next_tasks="$2"; shift 2 ;;
--worktree) worktree_path="$2"; shift 2 ;;
--branch) branch_name="$2"; shift 2 ;;
--batch) batch_name="$2"; shift 2 ;;
--note) note="$2"; shift 2 ;;
--elapsed) elapsed_mins="$2"; shift 2 ;;
--target) target_mins="$2"; shift 2 ;;
*) print_error "Unknown option: $1"; return 1 ;;
esac
Comment thread
coderabbitai[bot] marked this conversation as resolved.
done

ensure_dir

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

# Auto-detect git state if not provided
if [[ -z "$branch_name" ]]; then
branch_name="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")"
fi

# Build checkpoint file
cat > "$CHECKPOINT_FILE" <<EOF
# Session Checkpoint

Updated: ${timestamp}

## Current State

| Field | Value |
|-------|-------|
| Current Task | ${current_task:-none} |
| Branch | ${branch_name} |
| Worktree | ${worktree_path:-not set} |
| Batch/PR | ${batch_name:-not set} |
| Elapsed | ${elapsed_mins:-unknown} min |
| Target | ${target_mins:-unknown} min |

## Next Tasks

${next_tasks:-No next tasks specified}

## Context Note

${note:-No additional context}

## Git Status

$(git status --short 2>/dev/null || echo "Not in a git repo")

## Recent Commits (this branch)

$(git log --oneline -5 2>/dev/null || echo "No commits")

## Open Worktrees

$(git worktree list 2>/dev/null || echo "No worktrees")
EOF

print_success "Checkpoint saved: ${CHECKPOINT_FILE}"
print_info "Task: ${current_task:-none} | Branch: ${branch_name} | ${timestamp}"
return 0
}

cmd_load() {
if [[ ! -f "$CHECKPOINT_FILE" ]]; then
print_warning "No checkpoint found at ${CHECKPOINT_FILE}"
print_info "Run: session-checkpoint-helper.sh save --task <id> --next <ids>"
return 1
fi

cat "$CHECKPOINT_FILE"
return 0
}

cmd_clear() {
if [[ -f "$CHECKPOINT_FILE" ]]; then
rm "$CHECKPOINT_FILE"
print_success "Checkpoint cleared"
else
print_info "No checkpoint to clear"
fi
return 0
}

cmd_status() {
if [[ ! -f "$CHECKPOINT_FILE" ]]; then
print_warning "No active checkpoint"
return 1
fi

local file_age_seconds
local now
local file_mtime

now="$(date +%s)"
if [[ "$(uname)" == "Darwin" ]]; then
file_mtime="$(stat -f %m "$CHECKPOINT_FILE")"
else
file_mtime="$(stat -c %Y "$CHECKPOINT_FILE")"
fi
file_age_seconds=$(( now - file_mtime ))

local age_display
if [[ $file_age_seconds -lt 60 ]]; then
age_display="${file_age_seconds}s ago"
elif [[ $file_age_seconds -lt 3600 ]]; then
age_display="$(( file_age_seconds / 60 ))m ago"
else
age_display="$(( file_age_seconds / 3600 ))h $(( (file_age_seconds % 3600) / 60 ))m ago"
fi

# Extract key fields
local current_task
current_task="$(grep -m1 'Current Task' "$CHECKPOINT_FILE" | sed 's/.*| //' | sed 's/ *$//' || echo "unknown")"
local branch
branch="$(grep -m1 'Branch' "$CHECKPOINT_FILE" | sed 's/.*| //' | sed 's/ *$//' || echo "unknown")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The sed commands used to extract values from the checkpoint file are incorrect. They will leave a trailing | character in the extracted value because sed 's/ *$//' only removes trailing spaces, not the pipe character. A single, more robust sed command can correctly extract and trim the value for both variables.

Suggested change
current_task="$(grep -m1 'Current Task' "$CHECKPOINT_FILE" | sed 's/.*| //' | sed 's/ *$//' || echo "unknown")"
local branch
branch="$(grep -m1 'Branch' "$CHECKPOINT_FILE" | sed 's/.*| //' | sed 's/ *$//' || echo "unknown")"
current_task="$(grep -m1 'Current Task' "$CHECKPOINT_FILE" | sed 's/.*| *//; s/ *| *$//' || echo "unknown")"
local branch
branch="$(grep -m1 'Branch' "$CHECKPOINT_FILE" | sed 's/.*| *//; s/ *| *$//' || echo "unknown")"


printf '%b\n' "${BOLD}Checkpoint Status${NC}"
printf " Age: %s\n" "$age_display"
printf " Task: %s\n" "$current_task"
printf " Branch: %s\n" "$branch"
printf " File: %s\n" "$CHECKPOINT_FILE"

if [[ $file_age_seconds -gt 1800 ]]; then
print_warning " Warning: Checkpoint is stale (>30min). Consider updating."
fi
return 0
}

cmd_help() {
# Extract header comment block as help text
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
return 0
}

# Main dispatch
main() {
local command="${1:-help}"
shift 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The shift 2 command is incorrect here. It will consume the main command (e.g., save) and its first argument (e.g., --task), causing argument parsing to fail for any command that takes options. It should be shift or shift 1 to only consume the main command.

Suggested change
shift 2>/dev/null || true
shift || true

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

False positive. This is shift 2>/dev/null || true, not shift 2. The 2>/dev/null redirects stderr (fd 2) to /dev/null — it does not pass 2 as an argument to shift. This is a standard bash pattern: shift (no argument) consumes one positional parameter, and 2>/dev/null || true silently handles the case where there are no remaining parameters.


case "$command" in
save) cmd_save "$@" ;;
load) cmd_load ;;
clear) cmd_clear ;;
status) cmd_status ;;
help|-h|--help) cmd_help ;;
*)
print_error "Unknown command: ${command}"
cmd_help
return 1
;;
esac
}

main "$@"
Loading
Loading