@theglitchking/persistent-planning 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ #!/bin/bash
2
+ ###############################################################################
3
+ # init-phase.sh - Create a new phase in lg-mode persistent-planning
4
+ #
5
+ # Usage:
6
+ # bash scripts/init-phase.sh "Phase Name"
7
+ #
8
+ # Creates:
9
+ # .planning/<phase-slug>/phase.md (HEWTD-frontmattered phase template)
10
+ # .planning/<phase-slug>/notes.md (cross-cutting notes for the phase)
11
+ # .planning/.meta/workspace.json (mode = lg, on first phase init)
12
+ #
13
+ # Refuses to run unless mode is lg. Run init-planning.sh for sm-mode tasks.
14
+ ###############################################################################
15
+ set -e
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ source "${SCRIPT_DIR}/lib/planning.sh"
19
+
20
+ case "${1:-}" in
21
+ -h|--help)
22
+ cat <<EOF
23
+ Usage: bash scripts/init-phase.sh "Phase Name"
24
+
25
+ Creates:
26
+ .planning/<phase-slug>/phase.md
27
+ .planning/<phase-slug>/notes.md
28
+ .planning/.meta/workspace.json (on first phase init, with mode=lg)
29
+
30
+ Refuses to run unless mode is lg. Run init-planning.sh for sm-mode tasks.
31
+
32
+ Examples:
33
+ bash scripts/init-phase.sh "Foundation"
34
+ bash scripts/init-phase.sh "Multi-corpus refactor"
35
+ EOF
36
+ exit 0
37
+ ;;
38
+ "")
39
+ planning_err "Phase name required."
40
+ echo ""
41
+ echo "Usage: bash scripts/init-phase.sh \"Phase Name\""
42
+ echo " bash scripts/init-phase.sh --help"
43
+ exit 1
44
+ ;;
45
+ esac
46
+
47
+ PHASE_NAME="$1"
48
+ PHASE_SLUG=$(planning_slugify "$PHASE_NAME")
49
+
50
+ if [[ -z "$PHASE_SLUG" ]]; then
51
+ planning_err "Phase name must contain at least one alphanumeric character."
52
+ exit 1
53
+ fi
54
+
55
+ # Refuse to run in sm mode
56
+ MODE=$(planning_mode)
57
+ if [[ "$MODE" != "lg" ]]; then
58
+ planning_err "Current mode is '$MODE' but init-phase.sh requires lg mode."
59
+ echo ""
60
+ echo "Switch to lg mode by running:"
61
+ echo " /start-planning --mode lg \"<phase name>\""
62
+ echo "or by editing .planning/.meta/workspace.json directly."
63
+ exit 1
64
+ fi
65
+
66
+ ROOT=$(planning_root)
67
+ PLANNING="$(planning_dir)"
68
+ META="$(planning_meta_dir)"
69
+ WORKSPACE_JSON="$(planning_workspace_json)"
70
+ TODAY=$(planning_today)
71
+ PHASE_DIR="${PLANNING}/${PHASE_SLUG}"
72
+ TEMPLATE_DIR="${ROOT}/templates/lg"
73
+
74
+ # Fallback: when run from the installed plugin location, templates live in the
75
+ # plugin's own directory, not the project root.
76
+ if [[ ! -d "$TEMPLATE_DIR" ]]; then
77
+ TEMPLATE_DIR="${SCRIPT_DIR}/../templates/lg"
78
+ fi
79
+ if [[ ! -d "$TEMPLATE_DIR" ]]; then
80
+ planning_err "Cannot locate templates/lg directory. Tried:"
81
+ echo " ${ROOT}/templates/lg"
82
+ echo " ${SCRIPT_DIR}/../templates/lg"
83
+ exit 1
84
+ fi
85
+
86
+ planning_log "Initializing phase: ${PHASE_NAME}"
87
+
88
+ # Bootstrap .planning/.meta/workspace.json if missing
89
+ mkdir -p "$META"
90
+ if [[ ! -f "$WORKSPACE_JSON" ]]; then
91
+ CONTRIB=0
92
+ if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1; then
93
+ CONTRIB=$(git -C "$ROOT" log --since="90 days ago" --format='%ae' 2>/dev/null | sort -u | wc -l | tr -d ' ')
94
+ fi
95
+ cat > "$WORKSPACE_JSON" <<EOF
96
+ {
97
+ "schema_version": "1.0",
98
+ "mode": "lg",
99
+ "auto_detected": true,
100
+ "detected_contributors": ${CONTRIB},
101
+ "created_at": "$(date -Iseconds)"
102
+ }
103
+ EOF
104
+ planning_ok "Created .planning/.meta/workspace.json (mode=lg, contributors=${CONTRIB})"
105
+ fi
106
+
107
+ # Create phase directory
108
+ mkdir -p "$PHASE_DIR"
109
+
110
+ # Render phase.md
111
+ planning_render_and_log \
112
+ "${TEMPLATE_DIR}/phase.md" \
113
+ "${PHASE_DIR}/phase.md" \
114
+ "phase.md at ${PHASE_DIR}/phase.md" \
115
+ "PHASE_TITLE_PLACEHOLDER=${PHASE_NAME}" \
116
+ "PHASE_SLUG_PLACEHOLDER=${PHASE_SLUG}" \
117
+ "PHASE_DATE_PLACEHOLDER=${TODAY}"
118
+
119
+ # Render notes.md (scoped to the phase)
120
+ planning_render_and_log \
121
+ "${TEMPLATE_DIR}/notes.md" \
122
+ "${PHASE_DIR}/notes.md" \
123
+ "notes.md at ${PHASE_DIR}/notes.md" \
124
+ "NOTES_TITLE_PLACEHOLDER=${PHASE_NAME} — Notes" \
125
+ "NOTES_SCOPE_PLACEHOLDER=${PHASE_SLUG}" \
126
+ "NOTES_DATE_PLACEHOLDER=${TODAY}"
127
+
128
+ # Add .planning/ to .gitignore if missing (preserve existing behavior)
129
+ GITIGNORE="${ROOT}/.gitignore"
130
+ if [[ -f "$GITIGNORE" ]] && ! grep -q "^\.planning/" "$GITIGNORE"; then
131
+ echo ".planning/" >> "$GITIGNORE"
132
+ planning_ok "Added .planning/ to .gitignore"
133
+ fi
134
+
135
+ echo ""
136
+ planning_ok "Phase '${PHASE_NAME}' ready at .planning/${PHASE_SLUG}/"
137
+ echo ""
138
+ echo "Next steps:"
139
+ echo " 1. Edit .planning/${PHASE_SLUG}/phase.md to describe the phase goal"
140
+ echo " 2. Add tasks with: /start-task \"<task name>\" --parent ${PHASE_SLUG}"
141
+ echo " 3. Add cross-cutting context to .planning/${PHASE_SLUG}/notes.md"
@@ -0,0 +1,233 @@
1
+ #!/bin/bash
2
+
3
+ ###############################################################################
4
+ # init-planning.sh - Persistent Planning Directory Setup
5
+ #
6
+ # Part of the persistent-planning plugin by TheGlitchKing
7
+ # https://github.com/TheGlitchKing/persistent-planning
8
+ #
9
+ # Usage:
10
+ # bash scripts/init-planning.sh [task-name]
11
+ # bash scripts/init-planning.sh "Refactor database layer"
12
+ #
13
+ # What it does:
14
+ # 1. Creates .planning/[task-slug]/ directory (separate per task)
15
+ # 2. Creates task_plan.md with templates
16
+ # 3. Creates notes.md with templates
17
+ # 4. Adds .planning/ to .gitignore (optional)
18
+ # 5. Confirms creation and provides next steps
19
+ #
20
+ # Multiple tasks example:
21
+ # /start-planning "Refactor auth" -> .planning/refactor-auth/
22
+ # /start-planning "Debug memory leak" -> .planning/debug-memory-leak/
23
+ # Both plans coexist without conflicts!
24
+ ###############################################################################
25
+
26
+ set -e
27
+
28
+ # Validate that task name was provided
29
+ if [ -z "$1" ]; then
30
+ echo "Error: Task name required"
31
+ echo ""
32
+ echo "Usage: bash scripts/init-planning.sh \"Your Task Name\""
33
+ echo ""
34
+ echo "Examples:"
35
+ echo " bash scripts/init-planning.sh \"Refactor authentication\""
36
+ echo " bash scripts/init-planning.sh \"Debug API performance\""
37
+ exit 1
38
+ fi
39
+
40
+ TASK_NAME="$1"
41
+
42
+ # Convert task name to slug
43
+ # "Refactor Authentication" -> "refactor-authentication"
44
+ task_name_to_slug() {
45
+ local name="$1"
46
+ name=$(echo "$name" | tr '[:upper:]' '[:lower:]')
47
+ name=$(echo "$name" | sed 's/[[:space:]_]\+/-/g')
48
+ name=$(echo "$name" | sed 's/[^a-z0-9-]//g')
49
+ name=$(echo "$name" | sed 's/^-\+\|-\+$//g')
50
+ name=$(echo "$name" | sed 's/-\+/-/g')
51
+ echo "$name"
52
+ }
53
+
54
+ TASK_SLUG=$(task_name_to_slug "$TASK_NAME")
55
+
56
+ # Validate slug
57
+ if [ -z "$TASK_SLUG" ]; then
58
+ echo "Error: Task name must contain at least one alphanumeric character"
59
+ echo "Provided: $TASK_NAME"
60
+ exit 1
61
+ fi
62
+
63
+ # Determine project root
64
+ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
65
+ PLANNING_DIR="${PROJECT_ROOT}/.planning"
66
+ TASK_DIR="${PLANNING_DIR}/${TASK_SLUG}"
67
+
68
+ # Color codes
69
+ GREEN='\033[0;32m'
70
+ BLUE='\033[0;34m'
71
+ YELLOW='\033[1;33m'
72
+ CYAN='\033[0;36m'
73
+ NC='\033[0m'
74
+
75
+ echo -e "${BLUE}Initializing Planning Structure${NC}"
76
+ echo ""
77
+
78
+ # Step 1: Create .planning directory
79
+ if [ ! -d "$PLANNING_DIR" ]; then
80
+ mkdir -p "$PLANNING_DIR"
81
+ echo -e "${GREEN}+${NC} Created .planning/ directory"
82
+ else
83
+ echo -e "${YELLOW}*${NC} .planning/ directory exists"
84
+ fi
85
+
86
+ # Step 2: Create task directory
87
+ if [ ! -d "$TASK_DIR" ]; then
88
+ mkdir -p "$TASK_DIR"
89
+ echo -e "${GREEN}+${NC} Created task directory: ${CYAN}.planning/${TASK_SLUG}/${NC}"
90
+ else
91
+ echo -e "${YELLOW}*${NC} Task directory already exists: ${CYAN}.planning/${TASK_SLUG}/${NC}"
92
+ fi
93
+
94
+ # Step 3: Create task_plan.md
95
+ TASK_PLAN_FILE="${TASK_DIR}/task_plan.md"
96
+ if [ ! -f "$TASK_PLAN_FILE" ]; then
97
+ cat > "$TASK_PLAN_FILE" << 'EOF'
98
+ # Task Plan: TASK_NAME_PLACEHOLDER
99
+
100
+ ## Goal
101
+ [One sentence describing the end state]
102
+
103
+ ## Phases
104
+ - [ ] Phase 1: Plan and setup
105
+ - [ ] Phase 2: Research/gather information
106
+ - [ ] Phase 3: Execute/build
107
+ - [ ] Phase 4: Review and deliver
108
+
109
+ ## Key Questions
110
+ 1. [Question to answer]
111
+ 2. [Question to answer]
112
+
113
+ ## Decisions Made
114
+ - [Decision]: [Rationale]
115
+
116
+ ## Errors Encountered
117
+ - [Error]: [Resolution]
118
+
119
+ ## Status
120
+ **Currently in Phase 1** - Setting up planning structure
121
+
122
+ ---
123
+
124
+ ## Notes
125
+ - This file persists across sessions
126
+ - Update status after each phase
127
+ - Mark completed phases with [x]
128
+ - Save research to notes.md instead of stuffing context
129
+ EOF
130
+
131
+ sed -i "s/TASK_NAME_PLACEHOLDER/${TASK_NAME}/g" "$TASK_PLAN_FILE"
132
+ echo -e "${GREEN}+${NC} Created task_plan.md"
133
+ else
134
+ echo -e "${YELLOW}*${NC} task_plan.md already exists (not overwritten)"
135
+ fi
136
+
137
+ # Step 4: Create notes.md
138
+ NOTES_FILE="${TASK_DIR}/notes.md"
139
+ if [ ! -f "$NOTES_FILE" ]; then
140
+ cat > "$NOTES_FILE" << 'EOF'
141
+ # Notes: TASK_NAME_PLACEHOLDER
142
+
143
+ ## Key Findings
144
+ - [Finding 1]
145
+ - [Finding 2]
146
+
147
+ ## Research Sources
148
+ - [Source 1]: [URL or reference]
149
+ - [Source 2]: [URL or reference]
150
+
151
+ ## Synthesized Findings
152
+
153
+ ### [Category/Topic 1]
154
+ - [Finding]
155
+ - [Finding]
156
+
157
+ ### [Category/Topic 2]
158
+ - [Finding]
159
+ - [Finding]
160
+
161
+ ## Decisions Made
162
+ - [Decision]: [Rationale]
163
+ - [Decision]: [Rationale]
164
+
165
+ ## Errors & Solutions
166
+ - [Error]: [Resolution]
167
+ - [Error]: [Resolution]
168
+
169
+ ---
170
+
171
+ ## Append-Only Log
172
+ Store findings here as you discover them. This builds a searchable history.
173
+
174
+ ### [Date/Time]
175
+ - [What I found/did]
176
+ EOF
177
+
178
+ sed -i "s/TASK_NAME_PLACEHOLDER/${TASK_NAME}/g" "$NOTES_FILE"
179
+ echo -e "${GREEN}+${NC} Created notes.md"
180
+ else
181
+ echo -e "${YELLOW}*${NC} notes.md already exists (not overwritten)"
182
+ fi
183
+
184
+ # Step 5: Update .gitignore
185
+ if [ -f "${PROJECT_ROOT}/.gitignore" ]; then
186
+ if ! grep -q "^\.planning/" "${PROJECT_ROOT}/.gitignore"; then
187
+ echo ".planning/" >> "${PROJECT_ROOT}/.gitignore"
188
+ echo -e "${GREEN}+${NC} Added .planning/ to .gitignore"
189
+ else
190
+ echo -e "${YELLOW}*${NC} .planning/ already in .gitignore"
191
+ fi
192
+ else
193
+ echo -e "${YELLOW}*${NC} No .gitignore found (not added)"
194
+ fi
195
+
196
+ # Step 6: List other active tasks
197
+ OTHER_TASKS=$(find "$PLANNING_DIR" -maxdepth 1 -type d -not -name ".planning" -not -path "$TASK_DIR" 2>/dev/null | wc -l)
198
+ if [ "$OTHER_TASKS" -gt 0 ]; then
199
+ echo ""
200
+ echo -e "${CYAN}Other active tasks:${NC}"
201
+ find "$PLANNING_DIR" -maxdepth 1 -type d -not -name ".planning" -not -path "$TASK_DIR" | sort | while read dir; do
202
+ task_slug=$(basename "$dir")
203
+ echo " .planning/${task_slug}/"
204
+ done
205
+ fi
206
+
207
+ # Step 7: Confirmation
208
+ echo ""
209
+ echo -e "${GREEN}=====================================${NC}"
210
+ echo -e "${GREEN}Task Plan Ready${NC}"
211
+ echo -e "${GREEN}=====================================${NC}"
212
+ echo ""
213
+
214
+ echo -e "${BLUE}Task:${NC} ${TASK_NAME}"
215
+ echo -e "${BLUE}Location:${NC} .planning/${TASK_SLUG}/"
216
+ echo ""
217
+
218
+ echo -e "${BLUE}Files:${NC}"
219
+ echo " task_plan.md (your task plan)"
220
+ echo " notes.md (research & findings)"
221
+ echo ""
222
+
223
+ echo -e "${BLUE}Next steps:${NC}"
224
+ echo " 1. Edit .planning/${TASK_SLUG}/task_plan.md to define your phases"
225
+ echo " 2. As you work, update the Status section"
226
+ echo " 3. Save findings to .planning/${TASK_SLUG}/notes.md"
227
+ echo " 4. Re-read task_plan.md before major decisions"
228
+ echo ""
229
+
230
+ echo -e "${BLUE}Cleanup:${NC}"
231
+ echo " rm -rf .planning/${TASK_SLUG}/ # Remove this task"
232
+ echo " rm -rf .planning/ # Remove all tasks"
233
+ echo ""
@@ -0,0 +1,118 @@
1
+ #!/bin/bash
2
+ ###############################################################################
3
+ # init-task.sh - Create a new task under a phase in lg-mode persistent-planning
4
+ #
5
+ # Usage:
6
+ # bash scripts/init-task.sh "Task Name" --parent <phase-slug>
7
+ #
8
+ # Creates:
9
+ # .planning/<phase-slug>/<task-slug>/task.md
10
+ # .planning/<phase-slug>/<task-slug>/notes.md
11
+ # .planning/<phase-slug>/<task-slug>/atoms/ (empty dir for future atoms)
12
+ #
13
+ # Refuses if mode is not lg, or if --parent doesn't reference an existing phase.
14
+ ###############################################################################
15
+ set -e
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ source "${SCRIPT_DIR}/lib/planning.sh"
19
+
20
+ # Parse arguments
21
+ TASK_NAME=""
22
+ PARENT_PHASE=""
23
+ while [[ $# -gt 0 ]]; do
24
+ case "$1" in
25
+ --parent)
26
+ PARENT_PHASE="$2"
27
+ shift 2
28
+ ;;
29
+ -h|--help)
30
+ cat <<EOF
31
+ Usage: bash scripts/init-task.sh "Task Name" --parent <phase-slug>
32
+
33
+ Examples:
34
+ bash scripts/init-task.sh "HEWTD schema extension" --parent foundation
35
+ bash scripts/init-task.sh "Multi-corpus refactor" --parent semantic-memory-core
36
+ EOF
37
+ exit 0
38
+ ;;
39
+ *)
40
+ if [[ -z "$TASK_NAME" ]]; then
41
+ TASK_NAME="$1"
42
+ fi
43
+ shift
44
+ ;;
45
+ esac
46
+ done
47
+
48
+ if [[ -z "$TASK_NAME" || -z "$PARENT_PHASE" ]]; then
49
+ planning_err "Both task name and --parent <phase-slug> are required."
50
+ echo "Usage: bash scripts/init-task.sh \"Task Name\" --parent <phase-slug>"
51
+ exit 1
52
+ fi
53
+
54
+ TASK_SLUG=$(planning_slugify "$TASK_NAME")
55
+
56
+ if [[ -z "$TASK_SLUG" ]]; then
57
+ planning_err "Task name must contain at least one alphanumeric character."
58
+ exit 1
59
+ fi
60
+
61
+ # Refuse non-lg
62
+ MODE=$(planning_mode)
63
+ if [[ "$MODE" != "lg" ]]; then
64
+ planning_err "Current mode is '$MODE' but init-task.sh requires lg mode."
65
+ exit 1
66
+ fi
67
+
68
+ ROOT=$(planning_root)
69
+ PLANNING="$(planning_dir)"
70
+ TODAY=$(planning_today)
71
+ PHASE_DIR="${PLANNING}/${PARENT_PHASE}"
72
+ TASK_DIR="${PHASE_DIR}/${TASK_SLUG}"
73
+ TEMPLATE_DIR="${ROOT}/templates/lg"
74
+
75
+ if [[ ! -d "$TEMPLATE_DIR" ]]; then
76
+ TEMPLATE_DIR="${SCRIPT_DIR}/../templates/lg"
77
+ fi
78
+ if [[ ! -d "$TEMPLATE_DIR" ]]; then
79
+ planning_err "Cannot locate templates/lg directory."
80
+ exit 1
81
+ fi
82
+
83
+ # Validate parent phase exists
84
+ if [[ ! -f "${PHASE_DIR}/phase.md" ]]; then
85
+ planning_err "Parent phase not found: .planning/${PARENT_PHASE}/phase.md"
86
+ echo "Create the phase first with:"
87
+ echo " /start-planning \"<phase name>\" (which runs init-phase.sh in lg mode)"
88
+ exit 1
89
+ fi
90
+
91
+ planning_log "Initializing task: ${TASK_NAME} (parent: ${PARENT_PHASE})"
92
+
93
+ mkdir -p "${TASK_DIR}/atoms"
94
+
95
+ planning_render_and_log \
96
+ "${TEMPLATE_DIR}/task.md" \
97
+ "${TASK_DIR}/task.md" \
98
+ "task.md at ${TASK_DIR}/task.md" \
99
+ "TASK_TITLE_PLACEHOLDER=${TASK_NAME}" \
100
+ "TASK_SLUG_PLACEHOLDER=${TASK_SLUG}" \
101
+ "PHASE_SLUG_PLACEHOLDER=${PARENT_PHASE}" \
102
+ "TASK_DATE_PLACEHOLDER=${TODAY}"
103
+
104
+ planning_render_and_log \
105
+ "${TEMPLATE_DIR}/notes.md" \
106
+ "${TASK_DIR}/notes.md" \
107
+ "notes.md at ${TASK_DIR}/notes.md" \
108
+ "NOTES_TITLE_PLACEHOLDER=${TASK_NAME} — Notes" \
109
+ "NOTES_SCOPE_PLACEHOLDER=${PARENT_PHASE}/${TASK_SLUG}" \
110
+ "NOTES_DATE_PLACEHOLDER=${TODAY}"
111
+
112
+ echo ""
113
+ planning_ok "Task '${TASK_NAME}' ready at .planning/${PARENT_PHASE}/${TASK_SLUG}/"
114
+ echo ""
115
+ echo "Next steps:"
116
+ echo " 1. Edit .planning/${PARENT_PHASE}/${TASK_SLUG}/task.md to describe the goal"
117
+ echo " 2. Add atoms with: /start-atom \"<atom name>\" --parent ${TASK_SLUG}"
118
+ echo " (or add inline atom checkboxes in task.md for simple atoms)"
@@ -0,0 +1,161 @@
1
+ #!/bin/bash
2
+ ###############################################################################
3
+ # scripts/lib/planning.sh - Shared helpers for persistent-planning init scripts
4
+ #
5
+ # Source this file from init-phase.sh, init-task.sh, init-atom.sh, and any
6
+ # other planning-related script that needs slug, root, mode, or template
7
+ # rendering helpers.
8
+ #
9
+ # Usage:
10
+ # #!/bin/bash
11
+ # set -e
12
+ # SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ # source "${SCRIPT_DIR}/lib/planning.sh"
14
+ #
15
+ # Provides:
16
+ # - planning_slugify "Some Name" → "some-name" (echoed)
17
+ # - planning_root → prints project root (CLAUDE_PROJECT_DIR or pwd)
18
+ # - planning_dir → prints "<root>/.planning"
19
+ # - planning_meta_dir → prints "<root>/.planning/.meta"
20
+ # - planning_workspace_json → prints "<meta>/workspace.json"
21
+ # - planning_mode → prints active mode (sm | lg) via detect-mode.sh
22
+ # - planning_today → prints today's date as YYYY-MM-DD
23
+ # - planning_render_template "<src>" "<dest>" "PLACEHOLDER1=value1" "PLACEHOLDER2=value2" ...
24
+ # Renders a template file by sed-substituting PLACEHOLDER tokens.
25
+ # Refuses to overwrite existing dest unless PLANNING_FORCE=1 in env.
26
+ # - planning_color_<name> → ANSI color codes (green, blue, yellow, cyan, red, reset)
27
+ # (only set when stdout is a tty; empty otherwise)
28
+ # - Color-coded log helpers: planning_log, planning_ok, planning_warn, planning_err
29
+ ###############################################################################
30
+
31
+ # Color codes (only when stdout is a tty)
32
+ if [[ -t 1 ]]; then
33
+ planning_color_green='\033[0;32m'
34
+ planning_color_blue='\033[0;34m'
35
+ planning_color_yellow='\033[1;33m'
36
+ planning_color_cyan='\033[0;36m'
37
+ planning_color_red='\033[0;31m'
38
+ planning_color_reset='\033[0m'
39
+ else
40
+ planning_color_green=''
41
+ planning_color_blue=''
42
+ planning_color_yellow=''
43
+ planning_color_cyan=''
44
+ planning_color_red=''
45
+ planning_color_reset=''
46
+ fi
47
+
48
+ planning_log() { printf "${planning_color_blue}[planning]${planning_color_reset} %s\n" "$*"; }
49
+ planning_ok() { printf "${planning_color_green}[planning]${planning_color_reset} %s\n" "$*"; }
50
+ planning_warn() { printf "${planning_color_yellow}[planning]${planning_color_reset} %s\n" "$*"; }
51
+ planning_err() { printf "${planning_color_red}[planning]${planning_color_reset} %s\n" "$*" >&2; }
52
+
53
+ planning_slugify() {
54
+ local name="$1"
55
+ name=$(echo "$name" | tr '[:upper:]' '[:lower:]')
56
+ name=$(echo "$name" | sed 's/[[:space:]_]\+/-/g')
57
+ name=$(echo "$name" | sed 's/[^a-z0-9-]//g')
58
+ name=$(echo "$name" | sed 's/^-\+\|-\+$//g')
59
+ name=$(echo "$name" | sed 's/-\+/-/g')
60
+ echo "$name"
61
+ }
62
+
63
+ planning_root() {
64
+ echo "${CLAUDE_PROJECT_DIR:-$(pwd)}"
65
+ }
66
+
67
+ planning_dir() {
68
+ echo "$(planning_root)/.planning"
69
+ }
70
+
71
+ planning_meta_dir() {
72
+ echo "$(planning_dir)/.meta"
73
+ }
74
+
75
+ planning_workspace_json() {
76
+ echo "$(planning_meta_dir)/workspace.json"
77
+ }
78
+
79
+ planning_today() {
80
+ date '+%Y-%m-%d'
81
+ }
82
+
83
+ planning_mode() {
84
+ # Resolve via detect-mode.sh (in the same scripts/ dir as the caller's library).
85
+ local lib_dir
86
+ lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
87
+ local detect="${lib_dir}/../detect-mode.sh"
88
+ if [[ -x "$detect" ]]; then
89
+ bash "$detect"
90
+ else
91
+ echo "sm"
92
+ fi
93
+ }
94
+
95
+ # planning_render_template <template_src> <dest_path> <PLACEHOLDER1=value1> ...
96
+ # Renders a template file by sed-substituting placeholders.
97
+ #
98
+ # Return codes:
99
+ # 0 → rendered fresh
100
+ # 2 → skipped (dest already exists; pass PLANNING_FORCE=1 to overwrite)
101
+ # 1 → error (template not found, etc.)
102
+ #
103
+ # Callers can branch on the return code to log accurately.
104
+ planning_render_template() {
105
+ local src="$1"
106
+ local dest="$2"
107
+ shift 2
108
+
109
+ if [[ ! -f "$src" ]]; then
110
+ planning_err "Template not found: $src"
111
+ return 1
112
+ fi
113
+
114
+ if [[ -f "$dest" && "${PLANNING_FORCE:-0}" != "1" ]]; then
115
+ planning_warn "Destination already exists, skipping: $dest"
116
+ planning_warn " (re-run with PLANNING_FORCE=1 to overwrite)"
117
+ return 2
118
+ fi
119
+
120
+ # Copy template to dest
121
+ cp "$src" "$dest"
122
+
123
+ # Apply substitutions
124
+ for kv in "$@"; do
125
+ local key="${kv%%=*}"
126
+ local value="${kv#*=}"
127
+ # Use a safe delimiter (~) since values may contain /
128
+ # Escape & and ~ in value
129
+ local escaped_value
130
+ escaped_value=$(printf '%s\n' "$value" | sed -e 's/[\&~]/\\&/g')
131
+ sed -i "s~${key}~${escaped_value}~g" "$dest"
132
+ done
133
+ return 0
134
+ }
135
+
136
+ # planning_render_and_log <template_src> <dest_path> <relative_label> <PLACEHOLDER=val> ...
137
+ # Wraps planning_render_template with accurate logging:
138
+ # - Prints "Created <relative_label>" only when the template was actually rendered
139
+ # - Prints nothing extra when skipped (the warn from render_template already covered it)
140
+ # - Lets the script continue (set -e tolerant) regardless of skip vs render
141
+ planning_render_and_log() {
142
+ local src="$1"
143
+ local dest="$2"
144
+ local label="$3"
145
+ shift 3
146
+
147
+ local rc=0
148
+ planning_render_template "$src" "$dest" "$@" || rc=$?
149
+ case $rc in
150
+ 0)
151
+ planning_ok "Created ${label}"
152
+ ;;
153
+ 2)
154
+ # Already covered by the warn inside planning_render_template
155
+ ;;
156
+ *)
157
+ planning_err "Failed to render ${label} (rc=$rc)"
158
+ return $rc
159
+ ;;
160
+ esac
161
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ title: ATOM_TITLE_PLACEHOLDER
3
+ tier: plan
4
+ domains:
5
+ - planning
6
+ status: ready
7
+ last_updated: "ATOM_DATE_PLACEHOLDER"
8
+ plan_kind: atom
9
+ parent: TASK_SLUG_PLACEHOLDER
10
+ sequence: ATOM_SEQUENCE_PLACEHOLDER
11
+ ---
12
+
13
+ # Atom: ATOM_TITLE_PLACEHOLDER
14
+
15
+ **Task**: `TASK_SLUG_PLACEHOLDER`
16
+ **Sequence**: `ATOM_SEQUENCE_PLACEHOLDER` (atoms within a task are processed sequentially)
17
+
18
+ ## What to do
19
+ [Describe the atomic action a subagent should take. Single focused step. If the work
20
+ spans more than one logical operation, split into multiple atoms with sequential ordering.
21
+
22
+ This file IS the contract a subagent reads to know exactly what to do. Be specific.]
23
+
24
+ ## Inputs
25
+ [Files, data, or prior atom outputs this atom depends on.]
26
+
27
+ ## Expected outputs
28
+ [Files modified, files created, commands run, or state changed.]
29
+
30
+ ## Acceptance criteria
31
+ [How a subagent (or human reviewer) confirms this atom is done. Should be concrete
32
+ and verifiable.]
33
+
34
+ ## Status
35
+ **Currently ready** — waiting for a subagent to claim it.
36
+
37
+ Status enum: `ready | in_progress | done | blocked`
38
+
39
+ Status transitions:
40
+ - `ready → in_progress`: a subagent claims this atom
41
+ - `in_progress → done`: the subagent completes the work and verifies acceptance criteria
42
+ - `in_progress → blocked`: the subagent hits an obstacle (add a note explaining why
43
+ in a `## Blocker` section)
44
+ - `done → in_progress` (reopen): allowed when a follow-up correction is needed; adds
45
+ `reopened_at: <iso-timestamp>` field to frontmatter for audit