planning-with-files 3.10.2 → 3.11.2
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/extensions/planning-with-files/__tests__/plan-anchor.test.ts +228 -228
- package/extensions/planning-with-files/package.json +17 -17
- package/extensions/planning-with-files/runtime.ts +788 -788
- package/package.json +1 -1
- package/scripts/attest-plan.ps1 +137 -137
- package/scripts/attest-plan.sh +206 -206
- package/scripts/check-complete.ps1 +253 -253
- package/scripts/check-complete.sh +253 -253
- package/scripts/gate-stop.sh +32 -32
- package/scripts/ledger-append.ps1 +180 -180
- package/scripts/ledger-append.sh +337 -337
- package/scripts/ledger-summary.ps1 +128 -128
- package/scripts/phase-status.ps1 +175 -175
- package/scripts/phase-status.sh +158 -158
- package/scripts/session-catchup.py +876 -876
- package/scripts/set-active-plan.ps1 +51 -51
- package/scripts/set-active-plan.sh +50 -50
package/scripts/phase-status.sh
CHANGED
|
@@ -1,158 +1,158 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# planning-with-files: set the status of one phase in task_plan.md (v3).
|
|
3
|
-
#
|
|
4
|
-
# This is the ONLY sanctioned concurrent-safe writer of task_plan.md status
|
|
5
|
-
# lines. The orchestrator owns task_plan.md; workers NEVER edit it directly.
|
|
6
|
-
# All status edits go through this read-modify-write under an advisory flock on
|
|
7
|
-
# the <plan-dir>/.write_lock sentinel, with an atomic temp-file + mv swap so a
|
|
8
|
-
# torn write can never leave a half-rewritten plan on disk (architecture C4).
|
|
9
|
-
#
|
|
10
|
-
# Note: editing task_plan.md changes its SHA, so the orchestrator must
|
|
11
|
-
# re-attest at phase boundaries (see attest-plan.sh).
|
|
12
|
-
#
|
|
13
|
-
# Plan-dir resolution (via resolve-plan-dir.sh):
|
|
14
|
-
# 1. $PLAN_ID env var -> ./.planning/$PLAN_ID/
|
|
15
|
-
# 2. ./.planning/.active_plan
|
|
16
|
-
# 3. Newest ./.planning/<dir>/ by mtime
|
|
17
|
-
# 4. Legacy: project root ./task_plan.md
|
|
18
|
-
#
|
|
19
|
-
# Usage:
|
|
20
|
-
# sh scripts/phase-status.sh <phase-number> <pending|in_progress|complete>
|
|
21
|
-
#
|
|
22
|
-
# Exits 1 with a message if the phase does not exist or the status is invalid.
|
|
23
|
-
|
|
24
|
-
set -u
|
|
25
|
-
|
|
26
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
27
|
-
RESOLVER="${SCRIPT_DIR}/resolve-plan-dir.sh"
|
|
28
|
-
|
|
29
|
-
usage() {
|
|
30
|
-
printf "Usage: %s <phase-number> <pending|in_progress|complete>\n" "$0" >&2
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
resolve_plan_file() {
|
|
34
|
-
plan_dir=""
|
|
35
|
-
if [ -f "${RESOLVER}" ]; then
|
|
36
|
-
plan_dir="$(sh "${RESOLVER}" 2>/dev/null)"
|
|
37
|
-
fi
|
|
38
|
-
if [ -n "${plan_dir}" ] && [ -f "${plan_dir}/task_plan.md" ]; then
|
|
39
|
-
printf "%s\n" "${plan_dir}/task_plan.md"
|
|
40
|
-
return 0
|
|
41
|
-
fi
|
|
42
|
-
if [ -f "./task_plan.md" ]; then
|
|
43
|
-
printf "%s\n" "./task_plan.md"
|
|
44
|
-
return 0
|
|
45
|
-
fi
|
|
46
|
-
return 1
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
PHASE_NUM="${1:-}"
|
|
50
|
-
NEW_STATUS="${2:-}"
|
|
51
|
-
|
|
52
|
-
if [ -z "${PHASE_NUM}" ] || [ -z "${NEW_STATUS}" ]; then
|
|
53
|
-
usage
|
|
54
|
-
exit 1
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
# Validate phase number is a positive integer.
|
|
58
|
-
case "${PHASE_NUM}" in
|
|
59
|
-
''|*[!0-9]*)
|
|
60
|
-
printf "[phase-status] phase number must be a positive integer, got '%s'.\n" "${PHASE_NUM}" >&2
|
|
61
|
-
exit 1
|
|
62
|
-
;;
|
|
63
|
-
esac
|
|
64
|
-
|
|
65
|
-
# Validate status value against the allowlist.
|
|
66
|
-
case "${NEW_STATUS}" in
|
|
67
|
-
pending|in_progress|complete) : ;;
|
|
68
|
-
*)
|
|
69
|
-
printf "[phase-status] invalid status '%s' (allowed: pending, in_progress, complete).\n" "${NEW_STATUS}" >&2
|
|
70
|
-
exit 1
|
|
71
|
-
;;
|
|
72
|
-
esac
|
|
73
|
-
|
|
74
|
-
PLAN_FILE="$(resolve_plan_file)" || {
|
|
75
|
-
printf "[phase-status] No task_plan.md found. Create a plan first.\n" >&2
|
|
76
|
-
exit 1
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
PLAN_DIR="$(dirname "${PLAN_FILE}")"
|
|
80
|
-
LOCK_FILE="${PLAN_DIR}/.write_lock"
|
|
81
|
-
|
|
82
|
-
# Confirm the phase heading exists before touching the file.
|
|
83
|
-
if ! grep -q "### Phase ${PHASE_NUM}\b" "${PLAN_FILE}" 2>/dev/null; then
|
|
84
|
-
# Fall back to a looser match for headings like "### Phase 1:" where \b may
|
|
85
|
-
# not be honored by a minimal grep.
|
|
86
|
-
if ! grep -Eq "^### Phase ${PHASE_NUM}([^0-9]|$)" "${PLAN_FILE}" 2>/dev/null; then
|
|
87
|
-
printf "[phase-status] Phase %s not found in %s.\n" "${PHASE_NUM}" "${PLAN_FILE}" >&2
|
|
88
|
-
exit 1
|
|
89
|
-
fi
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
# Rewrite only the FIRST "**Status:**" line that follows the "### Phase N"
|
|
93
|
-
# heading. awk tracks whether we are inside the target phase block; once we
|
|
94
|
-
# rewrite its status line we stop matching so later phases are untouched.
|
|
95
|
-
rewrite() {
|
|
96
|
-
src="$1"
|
|
97
|
-
dst="$2"
|
|
98
|
-
awk -v target="${PHASE_NUM}" -v newstatus="${NEW_STATUS}" '
|
|
99
|
-
BEGIN { in_block = 0; done = 0 }
|
|
100
|
-
{
|
|
101
|
-
line = $0
|
|
102
|
-
if (line ~ /^### Phase /) {
|
|
103
|
-
# Extract the phase number right after "### Phase ".
|
|
104
|
-
rest = line
|
|
105
|
-
sub(/^### Phase /, "", rest)
|
|
106
|
-
num = rest
|
|
107
|
-
sub(/[^0-9].*$/, "", num)
|
|
108
|
-
if (num == target && done == 0) {
|
|
109
|
-
in_block = 1
|
|
110
|
-
} else {
|
|
111
|
-
in_block = 0
|
|
112
|
-
}
|
|
113
|
-
} else if (in_block == 1 && done == 0 && line ~ /\*\*Status:\*\*/) {
|
|
114
|
-
# Preserve leading whitespace/bullet before "**Status:**".
|
|
115
|
-
prefix = line
|
|
116
|
-
sub(/\*\*Status:\*\*.*$/, "", prefix)
|
|
117
|
-
line = prefix "**Status:** " newstatus
|
|
118
|
-
in_block = 0
|
|
119
|
-
done = 1
|
|
120
|
-
}
|
|
121
|
-
print line
|
|
122
|
-
}
|
|
123
|
-
END { if (done == 0) exit 3 }
|
|
124
|
-
' "${src}" > "${dst}"
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
TMP_FILE="${PLAN_FILE}.tmp.$$"
|
|
128
|
-
|
|
129
|
-
do_write() {
|
|
130
|
-
if ! rewrite "${PLAN_FILE}" "${TMP_FILE}"; then
|
|
131
|
-
rm -f "${TMP_FILE}" 2>/dev/null
|
|
132
|
-
printf "[phase-status] No **Status:** line found for Phase %s.\n" "${PHASE_NUM}" >&2
|
|
133
|
-
return 1
|
|
134
|
-
fi
|
|
135
|
-
mv -f "${TMP_FILE}" "${PLAN_FILE}"
|
|
136
|
-
return 0
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
rc=0
|
|
140
|
-
if command -v flock >/dev/null 2>&1; then
|
|
141
|
-
(
|
|
142
|
-
flock -w 5 9 || true
|
|
143
|
-
do_write
|
|
144
|
-
) 9>"${LOCK_FILE}" 2>/dev/null
|
|
145
|
-
rc=$?
|
|
146
|
-
rm -f "${LOCK_FILE}" 2>/dev/null || true
|
|
147
|
-
else
|
|
148
|
-
do_write
|
|
149
|
-
rc=$?
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
if [ "${rc}" -ne 0 ]; then
|
|
153
|
-
rm -f "${TMP_FILE}" 2>/dev/null
|
|
154
|
-
exit 1
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
printf "[phase-status] Phase %s -> %s in %s\n" "${PHASE_NUM}" "${NEW_STATUS}" "${PLAN_FILE}"
|
|
158
|
-
exit 0
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# planning-with-files: set the status of one phase in task_plan.md (v3).
|
|
3
|
+
#
|
|
4
|
+
# This is the ONLY sanctioned concurrent-safe writer of task_plan.md status
|
|
5
|
+
# lines. The orchestrator owns task_plan.md; workers NEVER edit it directly.
|
|
6
|
+
# All status edits go through this read-modify-write under an advisory flock on
|
|
7
|
+
# the <plan-dir>/.write_lock sentinel, with an atomic temp-file + mv swap so a
|
|
8
|
+
# torn write can never leave a half-rewritten plan on disk (architecture C4).
|
|
9
|
+
#
|
|
10
|
+
# Note: editing task_plan.md changes its SHA, so the orchestrator must
|
|
11
|
+
# re-attest at phase boundaries (see attest-plan.sh).
|
|
12
|
+
#
|
|
13
|
+
# Plan-dir resolution (via resolve-plan-dir.sh):
|
|
14
|
+
# 1. $PLAN_ID env var -> ./.planning/$PLAN_ID/
|
|
15
|
+
# 2. ./.planning/.active_plan
|
|
16
|
+
# 3. Newest ./.planning/<dir>/ by mtime
|
|
17
|
+
# 4. Legacy: project root ./task_plan.md
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# sh scripts/phase-status.sh <phase-number> <pending|in_progress|complete>
|
|
21
|
+
#
|
|
22
|
+
# Exits 1 with a message if the phase does not exist or the status is invalid.
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
27
|
+
RESOLVER="${SCRIPT_DIR}/resolve-plan-dir.sh"
|
|
28
|
+
|
|
29
|
+
usage() {
|
|
30
|
+
printf "Usage: %s <phase-number> <pending|in_progress|complete>\n" "$0" >&2
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resolve_plan_file() {
|
|
34
|
+
plan_dir=""
|
|
35
|
+
if [ -f "${RESOLVER}" ]; then
|
|
36
|
+
plan_dir="$(sh "${RESOLVER}" 2>/dev/null)"
|
|
37
|
+
fi
|
|
38
|
+
if [ -n "${plan_dir}" ] && [ -f "${plan_dir}/task_plan.md" ]; then
|
|
39
|
+
printf "%s\n" "${plan_dir}/task_plan.md"
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
if [ -f "./task_plan.md" ]; then
|
|
43
|
+
printf "%s\n" "./task_plan.md"
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
return 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
PHASE_NUM="${1:-}"
|
|
50
|
+
NEW_STATUS="${2:-}"
|
|
51
|
+
|
|
52
|
+
if [ -z "${PHASE_NUM}" ] || [ -z "${NEW_STATUS}" ]; then
|
|
53
|
+
usage
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Validate phase number is a positive integer.
|
|
58
|
+
case "${PHASE_NUM}" in
|
|
59
|
+
''|*[!0-9]*)
|
|
60
|
+
printf "[phase-status] phase number must be a positive integer, got '%s'.\n" "${PHASE_NUM}" >&2
|
|
61
|
+
exit 1
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
64
|
+
|
|
65
|
+
# Validate status value against the allowlist.
|
|
66
|
+
case "${NEW_STATUS}" in
|
|
67
|
+
pending|in_progress|complete) : ;;
|
|
68
|
+
*)
|
|
69
|
+
printf "[phase-status] invalid status '%s' (allowed: pending, in_progress, complete).\n" "${NEW_STATUS}" >&2
|
|
70
|
+
exit 1
|
|
71
|
+
;;
|
|
72
|
+
esac
|
|
73
|
+
|
|
74
|
+
PLAN_FILE="$(resolve_plan_file)" || {
|
|
75
|
+
printf "[phase-status] No task_plan.md found. Create a plan first.\n" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
PLAN_DIR="$(dirname "${PLAN_FILE}")"
|
|
80
|
+
LOCK_FILE="${PLAN_DIR}/.write_lock"
|
|
81
|
+
|
|
82
|
+
# Confirm the phase heading exists before touching the file.
|
|
83
|
+
if ! grep -q "### Phase ${PHASE_NUM}\b" "${PLAN_FILE}" 2>/dev/null; then
|
|
84
|
+
# Fall back to a looser match for headings like "### Phase 1:" where \b may
|
|
85
|
+
# not be honored by a minimal grep.
|
|
86
|
+
if ! grep -Eq "^### Phase ${PHASE_NUM}([^0-9]|$)" "${PLAN_FILE}" 2>/dev/null; then
|
|
87
|
+
printf "[phase-status] Phase %s not found in %s.\n" "${PHASE_NUM}" "${PLAN_FILE}" >&2
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Rewrite only the FIRST "**Status:**" line that follows the "### Phase N"
|
|
93
|
+
# heading. awk tracks whether we are inside the target phase block; once we
|
|
94
|
+
# rewrite its status line we stop matching so later phases are untouched.
|
|
95
|
+
rewrite() {
|
|
96
|
+
src="$1"
|
|
97
|
+
dst="$2"
|
|
98
|
+
awk -v target="${PHASE_NUM}" -v newstatus="${NEW_STATUS}" '
|
|
99
|
+
BEGIN { in_block = 0; done = 0 }
|
|
100
|
+
{
|
|
101
|
+
line = $0
|
|
102
|
+
if (line ~ /^### Phase /) {
|
|
103
|
+
# Extract the phase number right after "### Phase ".
|
|
104
|
+
rest = line
|
|
105
|
+
sub(/^### Phase /, "", rest)
|
|
106
|
+
num = rest
|
|
107
|
+
sub(/[^0-9].*$/, "", num)
|
|
108
|
+
if (num == target && done == 0) {
|
|
109
|
+
in_block = 1
|
|
110
|
+
} else {
|
|
111
|
+
in_block = 0
|
|
112
|
+
}
|
|
113
|
+
} else if (in_block == 1 && done == 0 && line ~ /\*\*Status:\*\*/) {
|
|
114
|
+
# Preserve leading whitespace/bullet before "**Status:**".
|
|
115
|
+
prefix = line
|
|
116
|
+
sub(/\*\*Status:\*\*.*$/, "", prefix)
|
|
117
|
+
line = prefix "**Status:** " newstatus
|
|
118
|
+
in_block = 0
|
|
119
|
+
done = 1
|
|
120
|
+
}
|
|
121
|
+
print line
|
|
122
|
+
}
|
|
123
|
+
END { if (done == 0) exit 3 }
|
|
124
|
+
' "${src}" > "${dst}"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
TMP_FILE="${PLAN_FILE}.tmp.$$"
|
|
128
|
+
|
|
129
|
+
do_write() {
|
|
130
|
+
if ! rewrite "${PLAN_FILE}" "${TMP_FILE}"; then
|
|
131
|
+
rm -f "${TMP_FILE}" 2>/dev/null
|
|
132
|
+
printf "[phase-status] No **Status:** line found for Phase %s.\n" "${PHASE_NUM}" >&2
|
|
133
|
+
return 1
|
|
134
|
+
fi
|
|
135
|
+
mv -f "${TMP_FILE}" "${PLAN_FILE}"
|
|
136
|
+
return 0
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
rc=0
|
|
140
|
+
if command -v flock >/dev/null 2>&1; then
|
|
141
|
+
(
|
|
142
|
+
flock -w 5 9 || true
|
|
143
|
+
do_write
|
|
144
|
+
) 9>"${LOCK_FILE}" 2>/dev/null
|
|
145
|
+
rc=$?
|
|
146
|
+
rm -f "${LOCK_FILE}" 2>/dev/null || true
|
|
147
|
+
else
|
|
148
|
+
do_write
|
|
149
|
+
rc=$?
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [ "${rc}" -ne 0 ]; then
|
|
153
|
+
rm -f "${TMP_FILE}" 2>/dev/null
|
|
154
|
+
exit 1
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
printf "[phase-status] Phase %s -> %s in %s\n" "${PHASE_NUM}" "${NEW_STATUS}" "${PLAN_FILE}"
|
|
158
|
+
exit 0
|