@theglitchking/persistent-planning 3.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +10 -0
- package/package.json +3 -2
- package/scripts/detect-mode.sh +67 -0
- package/scripts/init-atom.sh +139 -0
- package/scripts/init-phase.sh +141 -0
- package/scripts/init-planning.sh +233 -0
- package/scripts/init-task.sh +118 -0
- package/scripts/lib/planning.sh +161 -0
- package/templates/lg/atom.md +45 -0
- package/templates/lg/notes.md +40 -0
- package/templates/lg/phase.md +42 -0
- package/templates/lg/task.md +54 -0
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Official marketplace for persistent-planning - Persistent markdown-based planning for Claude Code. Context engineering pattern with task directories, progress tracking, and working memory that persists across sessions.",
|
|
9
|
-
"version": "3.0.
|
|
9
|
+
"version": "3.0.1"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "persistent-planning",
|
|
14
14
|
"description": "Persistent markdown-based planning for Claude Code. Uses on-disk markdown files as working memory for planning, progress tracking, and knowledge storage. Plans persist across sessions and support multiple concurrent tasks.",
|
|
15
|
-
"version": "3.0.
|
|
15
|
+
"version": "3.0.1",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TheGlitchKing"
|
|
18
18
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "persistent-planning",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Persistent markdown-based planning with task directories, progress tracking, and context engineering for multi-step Claude Code workflows",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TheGlitchKing"
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.0.1] - 2026-05-08
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Critical: lg-mode init scripts and templates were excluded from the npm package.** The 3.0.0 release had `scripts/link-skills.js` (singular file) in the package.json `files` array instead of `scripts/` (whole dir), and `templates/` was missing entirely. As a result, users installing v3.0.0 got the slash command markdown but the underlying `init-phase.sh`, `init-task.sh`, `init-atom.sh`, `detect-mode.sh`, `lib/planning.sh`, and the four lg-mode templates (`phase.md`, `task.md`, `atom.md`, `notes.md`) were all missing — meaning lg mode silently broke on first invocation.
|
|
9
|
+
|
|
10
|
+
This patch updates the `files` array to include `scripts/` and `templates/` directories, and bumps to 3.0.1. v3.0.0 should be considered broken; consumers should upgrade to 3.0.1 immediately.
|
|
11
|
+
|
|
12
|
+
### How this happened
|
|
13
|
+
The `files` array was the legacy v2 contents (which only needed `scripts/link-skills.js`). When v3.0 added new scripts and templates I missed updating the array. Caught when verifying the published 3.0.0 tarball contents.
|
|
14
|
+
|
|
5
15
|
## [3.0.0] - 2026-05-07
|
|
6
16
|
|
|
7
17
|
### Added — Lg-mode (large/team) layered planning
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theglitchking/persistent-planning",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Persistent markdown-based planning with task directories, progress tracking, and context engineering for multi-step Claude Code workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"bin/",
|
|
14
14
|
"commands/",
|
|
15
15
|
"hooks/",
|
|
16
|
-
"scripts/
|
|
16
|
+
"scripts/",
|
|
17
|
+
"templates/",
|
|
17
18
|
"skills/",
|
|
18
19
|
"docs/",
|
|
19
20
|
".claude-plugin/",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
###############################################################################
|
|
3
|
+
# detect-mode.sh - Sm/lg mode detection for persistent-planning v3.0
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash scripts/detect-mode.sh [--explain]
|
|
7
|
+
#
|
|
8
|
+
# Output:
|
|
9
|
+
# "sm" or "lg" on stdout. With --explain, also prints a one-line decision
|
|
10
|
+
# banner to stderr explaining which signals fired.
|
|
11
|
+
#
|
|
12
|
+
# Heuristic:
|
|
13
|
+
# - If $CLAUDE_PROJECT_DIR/.planning/.meta/workspace.json exists with a
|
|
14
|
+
# "mode" field, that wins (override is sticky).
|
|
15
|
+
# - Otherwise count distinct git authors over the last 90 days. Two or
|
|
16
|
+
# more distinct emails → "lg". Otherwise "sm".
|
|
17
|
+
# - If the repo isn't a git repo or git fails for any reason, default
|
|
18
|
+
# to "sm" (the safer/less-surprising option for solo flows).
|
|
19
|
+
#
|
|
20
|
+
# Why this design:
|
|
21
|
+
# - Solo devs get the historical sm experience with zero ceremony.
|
|
22
|
+
# - Multi-contributor projects get the layered model surface immediately.
|
|
23
|
+
# - The decision banner avoids "magic" — the user sees what fired.
|
|
24
|
+
# - The workspace.json override means a single --mode flag sticks for
|
|
25
|
+
# all subsequent runs (no flag needed every time).
|
|
26
|
+
###############################################################################
|
|
27
|
+
set -e
|
|
28
|
+
|
|
29
|
+
EXPLAIN=0
|
|
30
|
+
if [[ "${1:-}" == "--explain" ]]; then
|
|
31
|
+
EXPLAIN=1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
35
|
+
WORKSPACE_JSON="${PROJECT_ROOT}/.planning/.meta/workspace.json"
|
|
36
|
+
|
|
37
|
+
# 1. Override wins if workspace.json exists
|
|
38
|
+
if [[ -f "$WORKSPACE_JSON" ]]; then
|
|
39
|
+
# Naive jq-free read: grep the mode line. workspace.json is small/structured.
|
|
40
|
+
MODE_FROM_JSON=$(grep -oE '"mode"[[:space:]]*:[[:space:]]*"(sm|lg)"' "$WORKSPACE_JSON" | sed -E 's/.*"(sm|lg)".*/\1/')
|
|
41
|
+
if [[ "$MODE_FROM_JSON" == "sm" || "$MODE_FROM_JSON" == "lg" ]]; then
|
|
42
|
+
echo "$MODE_FROM_JSON"
|
|
43
|
+
if [[ $EXPLAIN -eq 1 ]]; then
|
|
44
|
+
echo "[mode] $MODE_FROM_JSON (from .planning/.meta/workspace.json)" >&2
|
|
45
|
+
fi
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 2. Auto-detect via git author count over 90 days
|
|
51
|
+
DETECTED="sm"
|
|
52
|
+
CONTRIB=0
|
|
53
|
+
if command -v git >/dev/null 2>&1 && git -C "$PROJECT_ROOT" rev-parse --git-dir >/dev/null 2>&1; then
|
|
54
|
+
CONTRIB=$(git -C "$PROJECT_ROOT" log --since="90 days ago" --format='%ae' 2>/dev/null | sort -u | wc -l | tr -d ' ')
|
|
55
|
+
if [[ "$CONTRIB" -ge 2 ]]; then
|
|
56
|
+
DETECTED="lg"
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo "$DETECTED"
|
|
61
|
+
if [[ $EXPLAIN -eq 1 ]]; then
|
|
62
|
+
if [[ "$DETECTED" == "lg" ]]; then
|
|
63
|
+
echo "[mode] lg (auto-detected: $CONTRIB distinct git authors over the last 90 days; threshold ≥ 2)" >&2
|
|
64
|
+
else
|
|
65
|
+
echo "[mode] sm (auto-detected: $CONTRIB distinct git authors over the last 90 days; threshold for lg is ≥ 2)" >&2
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
###############################################################################
|
|
3
|
+
# init-atom.sh - Create a new atom (subagent hand-off unit) under a task in lg mode
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# bash scripts/init-atom.sh "Atom Name" --parent <task-slug>
|
|
7
|
+
#
|
|
8
|
+
# Creates:
|
|
9
|
+
# .planning/<phase-slug>/<task-slug>/atoms/<atom-slug>.md
|
|
10
|
+
#
|
|
11
|
+
# The atom's sequence number is auto-assigned: 1 + (max existing sequence in
|
|
12
|
+
# the same task's atoms/ directory). Atoms within a task default to sequential
|
|
13
|
+
# processing.
|
|
14
|
+
#
|
|
15
|
+
# --parent must be a task-slug. The phase is auto-resolved by walking the
|
|
16
|
+
# .planning/ tree to find the task.
|
|
17
|
+
###############################################################################
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
source "${SCRIPT_DIR}/lib/planning.sh"
|
|
22
|
+
|
|
23
|
+
# Parse arguments
|
|
24
|
+
ATOM_NAME=""
|
|
25
|
+
PARENT_TASK=""
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
--parent)
|
|
29
|
+
PARENT_TASK="$2"
|
|
30
|
+
shift 2
|
|
31
|
+
;;
|
|
32
|
+
-h|--help)
|
|
33
|
+
cat <<EOF
|
|
34
|
+
Usage: bash scripts/init-atom.sh "Atom Name" --parent <task-slug>
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
bash scripts/init-atom.sh "Update zod schema" --parent hewtd-schema-extension
|
|
38
|
+
bash scripts/init-atom.sh "Add plan-tier tests" --parent hewtd-schema-extension
|
|
39
|
+
EOF
|
|
40
|
+
exit 0
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
if [[ -z "$ATOM_NAME" ]]; then
|
|
44
|
+
ATOM_NAME="$1"
|
|
45
|
+
fi
|
|
46
|
+
shift
|
|
47
|
+
;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
if [[ -z "$ATOM_NAME" || -z "$PARENT_TASK" ]]; then
|
|
52
|
+
planning_err "Both atom name and --parent <task-slug> are required."
|
|
53
|
+
echo "Usage: bash scripts/init-atom.sh \"Atom Name\" --parent <task-slug>"
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
ATOM_SLUG=$(planning_slugify "$ATOM_NAME")
|
|
58
|
+
|
|
59
|
+
if [[ -z "$ATOM_SLUG" ]]; then
|
|
60
|
+
planning_err "Atom name must contain at least one alphanumeric character."
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
MODE=$(planning_mode)
|
|
65
|
+
if [[ "$MODE" != "lg" ]]; then
|
|
66
|
+
planning_err "Current mode is '$MODE' but init-atom.sh requires lg mode."
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
ROOT=$(planning_root)
|
|
71
|
+
PLANNING="$(planning_dir)"
|
|
72
|
+
TODAY=$(planning_today)
|
|
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
|
+
# Find the parent task — walk .planning/<phase>/<task>/task.md
|
|
84
|
+
TASK_DIR=""
|
|
85
|
+
for phase_path in "${PLANNING}"/*/; do
|
|
86
|
+
candidate="${phase_path%/}/${PARENT_TASK}"
|
|
87
|
+
if [[ -f "${candidate}/task.md" ]]; then
|
|
88
|
+
TASK_DIR="$candidate"
|
|
89
|
+
break
|
|
90
|
+
fi
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
if [[ -z "$TASK_DIR" ]]; then
|
|
94
|
+
planning_err "Parent task '${PARENT_TASK}' not found under .planning/<phase>/."
|
|
95
|
+
echo ""
|
|
96
|
+
echo "Available tasks:"
|
|
97
|
+
for phase_path in "${PLANNING}"/*/; do
|
|
98
|
+
phase_slug=$(basename "$phase_path")
|
|
99
|
+
[[ "$phase_slug" == ".meta" || "$phase_slug" == "archive" ]] && continue
|
|
100
|
+
for task_path in "${phase_path%/}"/*/; do
|
|
101
|
+
task_slug=$(basename "$task_path")
|
|
102
|
+
if [[ -f "${task_path}task.md" ]]; then
|
|
103
|
+
echo " --parent ${task_slug} (in phase: ${phase_slug})"
|
|
104
|
+
fi
|
|
105
|
+
done
|
|
106
|
+
done
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
ATOMS_DIR="${TASK_DIR}/atoms"
|
|
111
|
+
mkdir -p "$ATOMS_DIR"
|
|
112
|
+
|
|
113
|
+
# Auto-assign sequence: 1 + max existing sequence in the atoms/ dir
|
|
114
|
+
NEXT_SEQUENCE=1
|
|
115
|
+
if compgen -G "${ATOMS_DIR}/*.md" >/dev/null 2>&1; then
|
|
116
|
+
MAX_SEQ=$(grep -h '^sequence:' "${ATOMS_DIR}"/*.md 2>/dev/null | grep -oE '[0-9]+' | sort -n | tail -1)
|
|
117
|
+
if [[ -n "$MAX_SEQ" ]]; then
|
|
118
|
+
NEXT_SEQUENCE=$((MAX_SEQ + 1))
|
|
119
|
+
fi
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
planning_log "Initializing atom: ${ATOM_NAME} (parent: ${PARENT_TASK}, sequence: ${NEXT_SEQUENCE})"
|
|
123
|
+
|
|
124
|
+
planning_render_and_log \
|
|
125
|
+
"${TEMPLATE_DIR}/atom.md" \
|
|
126
|
+
"${ATOMS_DIR}/${ATOM_SLUG}.md" \
|
|
127
|
+
"atom at ${ATOMS_DIR}/${ATOM_SLUG}.md (sequence: ${NEXT_SEQUENCE})" \
|
|
128
|
+
"ATOM_TITLE_PLACEHOLDER=${ATOM_NAME}" \
|
|
129
|
+
"TASK_SLUG_PLACEHOLDER=${PARENT_TASK}" \
|
|
130
|
+
"ATOM_DATE_PLACEHOLDER=${TODAY}" \
|
|
131
|
+
"ATOM_SEQUENCE_PLACEHOLDER=${NEXT_SEQUENCE}"
|
|
132
|
+
|
|
133
|
+
echo ""
|
|
134
|
+
planning_ok "Atom '${ATOM_NAME}' ready"
|
|
135
|
+
echo ""
|
|
136
|
+
echo "Next steps:"
|
|
137
|
+
echo " 1. Edit ${ATOMS_DIR}/${ATOM_SLUG}.md to define what to do, inputs, outputs, and acceptance criteria"
|
|
138
|
+
echo " 2. A subagent will pick up this atom when status: ready"
|
|
139
|
+
echo " 3. Status transitions: ready → in_progress → done (or → blocked)"
|
|
@@ -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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NOTES_TITLE_PLACEHOLDER
|
|
3
|
+
tier: plan
|
|
4
|
+
domains:
|
|
5
|
+
- planning
|
|
6
|
+
status: active
|
|
7
|
+
last_updated: "NOTES_DATE_PLACEHOLDER"
|
|
8
|
+
plan_kind: notes
|
|
9
|
+
scope: NOTES_SCOPE_PLACEHOLDER
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Notes: NOTES_TITLE_PLACEHOLDER
|
|
13
|
+
|
|
14
|
+
**Scope**: `NOTES_SCOPE_PLACEHOLDER` (which phase / task this notes file is attached to)
|
|
15
|
+
|
|
16
|
+
## Key Findings
|
|
17
|
+
[Findings that emerged during plan implementation. Things that didn't fit cleanly into
|
|
18
|
+
a task or atom but matter for context.]
|
|
19
|
+
|
|
20
|
+
## Research Sources
|
|
21
|
+
[External references, internal docs, conversations that informed decisions.]
|
|
22
|
+
|
|
23
|
+
## Synthesized Findings
|
|
24
|
+
[Patterns, themes, distilled understanding from the research.]
|
|
25
|
+
|
|
26
|
+
## Decisions Made
|
|
27
|
+
[Notes-level decisions that affect the parent task or phase. Promote to the parent's
|
|
28
|
+
Decisions Made section if they have wider impact.]
|
|
29
|
+
|
|
30
|
+
## Errors & Solutions
|
|
31
|
+
[Errors encountered + how they were resolved. Useful for future similar work.]
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Append-Only Log
|
|
36
|
+
[Chronological record of progress. Each entry is dated. Useful for handoff and
|
|
37
|
+
reconstructing context after time away.]
|
|
38
|
+
|
|
39
|
+
### NOTES_DATE_PLACEHOLDER — Initialized
|
|
40
|
+
- Notes file created
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: PHASE_TITLE_PLACEHOLDER
|
|
3
|
+
tier: plan
|
|
4
|
+
domains:
|
|
5
|
+
- planning
|
|
6
|
+
status: draft
|
|
7
|
+
last_updated: "PHASE_DATE_PLACEHOLDER"
|
|
8
|
+
plan_kind: phase
|
|
9
|
+
parent: null
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Phase: PHASE_TITLE_PLACEHOLDER
|
|
13
|
+
|
|
14
|
+
## Goal
|
|
15
|
+
[One sentence describing what this phase achieves and the strategic boundary it represents.]
|
|
16
|
+
|
|
17
|
+
## Tasks
|
|
18
|
+
[Tasks created with `/start-task --parent PHASE_SLUG_PLACEHOLDER` will be tracked here.
|
|
19
|
+
Each task is a bounded deliverable. Tasks default to dependencies-first scheduling.
|
|
20
|
+
Tasks with no inter-dependencies can be marked `parallelizable: true` in their frontmatter
|
|
21
|
+
so subagent teams can pick them up concurrently.]
|
|
22
|
+
|
|
23
|
+
- [ ] (no tasks yet — run `/start-task "Task name" --parent PHASE_SLUG_PLACEHOLDER`)
|
|
24
|
+
|
|
25
|
+
## Decisions Made
|
|
26
|
+
[Phase-level decisions that affect multiple tasks. Format: `**Decision**: rationale`.]
|
|
27
|
+
|
|
28
|
+
## Status
|
|
29
|
+
**Currently draft** — phase has been initialized but no tasks defined.
|
|
30
|
+
|
|
31
|
+
Status enum: `draft | active | paused | done | archived`
|
|
32
|
+
|
|
33
|
+
When all tasks are `done`, mark this phase `done`. When archived, this directory and
|
|
34
|
+
its tasks/atoms move to `.planning/archive/PHASE_SLUG_PLACEHOLDER/`.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Layer reference (lg mode)
|
|
39
|
+
- **Phase** (this file): strategic grouping of related tasks
|
|
40
|
+
- **Task** (`.planning/PHASE_SLUG_PLACEHOLDER/<task-slug>/task.md`): bounded deliverable
|
|
41
|
+
- **Atom** (`.planning/PHASE_SLUG_PLACEHOLDER/<task-slug>/atoms/<atom-slug>.md`): subagent hand-off unit
|
|
42
|
+
- **Notes** (`.planning/PHASE_SLUG_PLACEHOLDER/notes.md` or per-task `notes.md`): cross-cutting references
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TASK_TITLE_PLACEHOLDER
|
|
3
|
+
tier: plan
|
|
4
|
+
domains:
|
|
5
|
+
- planning
|
|
6
|
+
status: draft
|
|
7
|
+
last_updated: "TASK_DATE_PLACEHOLDER"
|
|
8
|
+
plan_kind: task
|
|
9
|
+
parent: PHASE_SLUG_PLACEHOLDER
|
|
10
|
+
depends_on: []
|
|
11
|
+
parallelizable: false
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Task: TASK_TITLE_PLACEHOLDER
|
|
15
|
+
|
|
16
|
+
**Phase**: `PHASE_SLUG_PLACEHOLDER`
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
[One sentence describing the deliverable end-state for this task.]
|
|
20
|
+
|
|
21
|
+
## Atoms
|
|
22
|
+
[Atomic checkbox items that will be picked up by subagents. Each atom should be
|
|
23
|
+
something a single subagent can complete in one focused pass.
|
|
24
|
+
|
|
25
|
+
For complex atoms that need explicit subagent context, spawn a standalone atom file
|
|
26
|
+
via `/start-atom "Atom name" --parent TASK_SLUG_PLACEHOLDER` — these become files
|
|
27
|
+
under `atoms/` that subagents read via the planning MCP.
|
|
28
|
+
|
|
29
|
+
Inline checkbox vs. standalone atom file — see anti-pattern doc:
|
|
30
|
+
docs/atom-granularity.md.]
|
|
31
|
+
|
|
32
|
+
- [ ] (no atoms yet — add inline checkboxes here for simple atoms,
|
|
33
|
+
or run `/start-atom "Atom name" --parent TASK_SLUG_PLACEHOLDER` for complex ones)
|
|
34
|
+
|
|
35
|
+
## Decisions Made
|
|
36
|
+
[Task-level decisions. Format: `**Decision**: rationale`.]
|
|
37
|
+
|
|
38
|
+
## Status
|
|
39
|
+
**Currently draft** — task has been initialized but no atoms defined.
|
|
40
|
+
|
|
41
|
+
Status enum: `draft | active | paused | done | archived`
|
|
42
|
+
|
|
43
|
+
## Dependencies
|
|
44
|
+
This task does not currently depend on other tasks. Edit `depends_on:` in frontmatter
|
|
45
|
+
to declare dependencies on other tasks within the same phase. Tasks with no
|
|
46
|
+
inter-dependencies should set `parallelizable: true`.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Layer reference
|
|
51
|
+
- **Phase** (`../phase.md`): the strategic grouping this task belongs to
|
|
52
|
+
- **This task**: the bounded deliverable
|
|
53
|
+
- **Atoms** (`atoms/<atom-slug>.md`): subagent hand-off units (sequential within this task)
|
|
54
|
+
- **Notes** (`notes.md`): cross-cutting references for this task's implementation
|