@theglitchking/persistent-planning 3.1.0 → 3.4.0
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/.documentation/INDEX.md +15 -7
- package/.documentation/REGISTRY.md +2 -2
- package/.documentation/agents/INDEX.md +2 -2
- package/.documentation/agents/REGISTRY.md +2 -2
- package/.documentation/api/INDEX.md +2 -2
- package/.documentation/api/REGISTRY.md +2 -2
- package/.documentation/architecture/INDEX.md +5 -3
- package/.documentation/architecture/REGISTRY.md +8 -4
- package/.documentation/architecture/lg-mode.md +16 -0
- package/.documentation/architecture/lg-plan-artifact-lifecycle.md +104 -0
- package/.documentation/architecture/skill-linking-and-reclaim.md +84 -0
- package/.documentation/backups/INDEX.md +2 -2
- package/.documentation/backups/REGISTRY.md +2 -2
- package/.documentation/database/INDEX.md +2 -2
- package/.documentation/database/REGISTRY.md +2 -2
- package/.documentation/devops/INDEX.md +2 -2
- package/.documentation/devops/REGISTRY.md +2 -2
- package/.documentation/features/INDEX.md +2 -2
- package/.documentation/features/REGISTRY.md +2 -2
- package/.documentation/plans/INDEX.md +2 -2
- package/.documentation/plans/REGISTRY.md +2 -2
- package/.documentation/procedures/INDEX.md +4 -3
- package/.documentation/procedures/REGISTRY.md +6 -4
- package/.documentation/procedures/plan-completion-and-archive.md +34 -0
- package/.documentation/procedures/plugin-update-delivery.md +99 -0
- package/.documentation/quickstart/INDEX.md +2 -2
- package/.documentation/quickstart/REGISTRY.md +2 -2
- package/.documentation/reference/INDEX.md +10 -3
- package/.documentation/reference/REGISTRY.md +8 -4
- package/.documentation/reference/mandatory-frontmatter.md +71 -0
- package/.documentation/reference/skill-version-marker.md +70 -0
- package/.documentation/security/INDEX.md +2 -2
- package/.documentation/security/REGISTRY.md +2 -2
- package/.documentation/standards/INDEX.md +2 -2
- package/.documentation/standards/REGISTRY.md +2 -2
- package/.documentation/standards/mandatory-closing-phases.md +21 -1
- package/.documentation/testing/INDEX.md +2 -2
- package/.documentation/testing/REGISTRY.md +2 -2
- package/.documentation/testing/test-suite.md +38 -0
- package/.documentation/troubleshooting/INDEX.md +8 -8
- package/.documentation/troubleshooting/REGISTRY.md +20 -5
- package/.documentation/troubleshooting/plan-never-reads-complete.md +109 -0
- package/.documentation/troubleshooting/stale-skill-dir-drift.md +115 -0
- package/.documentation/workflows/INDEX.md +2 -2
- package/.documentation/workflows/REGISTRY.md +2 -2
- package/CHANGELOG.md +149 -0
- package/bin/persistent-planning.js +56 -6
- package/hooks/session-start.js +115 -3
- package/package.json +1 -1
- package/scripts/archive-plan.sh +3 -6
- package/scripts/init-atom.sh +7 -0
- package/scripts/init-phase.sh +58 -6
- package/scripts/init-planning.sh +18 -6
- package/scripts/init-task.sh +7 -0
- package/scripts/lib/planning.sh +122 -0
- package/scripts/lib/skill-link.js +177 -0
- package/scripts/link-skills.js +38 -0
- package/scripts/plan-status.sh +85 -10
- package/skills/persistent-planning/SKILL.md +5 -2
- package/templates/lg/phase.md +7 -4
- package/templates/lg/task.md +3 -2
package/scripts/link-skills.js
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Postinstall — delegates to @theglitchking/claude-plugin-runtime.
|
|
3
3
|
// See https://github.com/TheGlitchKing/claude-plugin-runtime/blob/main/docs/PLUGIN_AUTHORING_SCAFFOLD.md
|
|
4
|
+
//
|
|
5
|
+
// One thing runs before the delegation: reclaimStaleSkillDirs(). The runtime's linker
|
|
6
|
+
// skips a destination that is a real directory rather than a symlink, and skips it
|
|
7
|
+
// forever — so v1 leftovers never heal. Renaming the real dir aside first leaves a
|
|
8
|
+
// clean destination the runtime then links normally.
|
|
4
9
|
|
|
5
10
|
import { runPostinstall } from "@theglitchking/claude-plugin-runtime";
|
|
6
11
|
import { dirname, resolve } from "node:path";
|
|
7
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { reclaimStaleSkillDirs, writeVersionMarkers, ENV_PREFIX } from "./lib/skill-link.js";
|
|
14
|
+
import { createRequire } from "node:module";
|
|
8
15
|
|
|
9
16
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
const consumerRoot = process.env.INIT_CWD || process.cwd();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Skip when the runtime itself would skip: dev-in-place, or linking disabled.
|
|
21
|
+
const devInPlace = resolve(consumerRoot) === resolve(packageRoot);
|
|
22
|
+
if (!devInPlace && process.env[`${ENV_PREFIX}_SKIP_LINK`] !== "1") {
|
|
23
|
+
const r = reclaimStaleSkillDirs(consumerRoot, packageRoot, {
|
|
24
|
+
skillsSubdir: "skills",
|
|
25
|
+
log: (m) => console.log(m),
|
|
26
|
+
});
|
|
27
|
+
if (r.reclaimed.length) {
|
|
28
|
+
console.log(
|
|
29
|
+
`[persistent-planning] reclaimed ${r.reclaimed.length} stale skill dir(s); ` +
|
|
30
|
+
`originals kept alongside as .bak-* — delete them once you are satisfied.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// Reclaim is best-effort. A failure here must never block the install.
|
|
36
|
+
console.warn(`[persistent-planning] skill reclaim skipped: ${err?.message || err}`);
|
|
37
|
+
}
|
|
10
38
|
|
|
11
39
|
try {
|
|
12
40
|
runPostinstall({
|
|
@@ -21,3 +49,13 @@ try {
|
|
|
21
49
|
} catch (err) {
|
|
22
50
|
console.warn(`[persistent-planning] postinstall failed: ${err?.message || err}`);
|
|
23
51
|
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Stamp the artifact that actually executes, so drift is detectable at all.
|
|
55
|
+
// Only healthy symlinks are stamped — never a real dir, which would mark stale
|
|
56
|
+
// code as current.
|
|
57
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
58
|
+
writeVersionMarkers(consumerRoot, packageRoot, version, { skillsSubdir: "skills" });
|
|
59
|
+
} catch {
|
|
60
|
+
// Marker is a diagnostic aid, not a dependency. Absent is a valid state.
|
|
61
|
+
}
|
package/scripts/plan-status.sh
CHANGED
|
@@ -45,22 +45,91 @@ plan_root_artifact() {
|
|
|
45
45
|
done
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
# frontmatter_field <file> <key> -> value of `<key>:` inside the leading --- block
|
|
49
|
+
#
|
|
50
|
+
# Scoped to the frontmatter on purpose. Grepping a whole file for a frontmatter field
|
|
51
|
+
# matches it in prose, tables and fenced examples too — a notes.md documenting the
|
|
52
|
+
# contract then reads as an artifact that declares it (issue #14).
|
|
53
|
+
frontmatter_field() {
|
|
54
|
+
[[ -f "$1" ]] || return
|
|
55
|
+
awk -v key="$2" '
|
|
56
|
+
NR==1 && $0 != "---" { exit }
|
|
57
|
+
NR>1 && $0 == "---" { exit }
|
|
58
|
+
index($0, key ":") == 1 {
|
|
59
|
+
sub("^" key ":[[:space:]]*", "")
|
|
60
|
+
gsub(/["\047]/, "")
|
|
61
|
+
sub(/[[:space:]]+$/, "")
|
|
62
|
+
print; exit
|
|
63
|
+
}' "$1"
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
# frontmatter_status <file> -> value of `status:` inside the leading --- block
|
|
49
67
|
frontmatter_status() {
|
|
50
|
-
|
|
51
|
-
awk 'NR==1 && $0 != "---" { exit }
|
|
52
|
-
NR>1 && $0 == "---" { exit }
|
|
53
|
-
/^status:[[:space:]]*/ { sub(/^status:[[:space:]]*/, ""); gsub(/["\047]/, ""); print; exit }' "$1"
|
|
68
|
+
frontmatter_field "$1" "status"
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
# Emits "<total> <checked>" for every markdown file under a plan directory.
|
|
72
|
+
#
|
|
73
|
+
# Checkboxes inside fenced code blocks are examples, not work. A plan whose notes.md
|
|
74
|
+
# quotes markdown — which is what notes.md is for — used to have those quoted boxes
|
|
75
|
+
# counted against it, and a quoted `- [ ]` inflates only the denominator, so the plan
|
|
76
|
+
# became permanently uncompletable with no visible reason (issue #14).
|
|
77
|
+
#
|
|
78
|
+
# An unterminated fence swallows the rest of the file. That is the safe direction:
|
|
79
|
+
# ambiguous content is not counted as outstanding work.
|
|
57
80
|
count_boxes() {
|
|
58
|
-
local total checked
|
|
59
|
-
|
|
60
|
-
|
|
81
|
+
local total=0 checked=0 f out
|
|
82
|
+
while IFS= read -r f; do
|
|
83
|
+
[[ -n "$f" ]] || continue
|
|
84
|
+
out=$(awk '
|
|
85
|
+
# Fence open/close: ``` or ~~~, optionally indented, optional info string.
|
|
86
|
+
# A fence closes only on the same character.
|
|
87
|
+
/^[[:space:]]*(```|~~~)/ {
|
|
88
|
+
line = $0
|
|
89
|
+
sub(/^[[:space:]]*/, "", line)
|
|
90
|
+
ch = substr(line, 1, 1)
|
|
91
|
+
if (!infence) { infence = 1; fencechar = ch }
|
|
92
|
+
else if (ch == fencechar) { infence = 0 }
|
|
93
|
+
next
|
|
94
|
+
}
|
|
95
|
+
infence { next }
|
|
96
|
+
/^[[:space:]]*- \[[ xX]\] / { t++ }
|
|
97
|
+
/^[[:space:]]*- \[[xX]\] / { c++ }
|
|
98
|
+
END { print t + 0, c + 0 }
|
|
99
|
+
' "$f" 2>/dev/null) || continue
|
|
100
|
+
total=$((total + ${out%% *}))
|
|
101
|
+
checked=$((checked + ${out##* }))
|
|
102
|
+
done < <(find "$1" -type f -name '*.md' 2>/dev/null)
|
|
61
103
|
echo "$total $checked"
|
|
62
104
|
}
|
|
63
105
|
|
|
106
|
+
# Files under a plan whose FRONTMATTER declares <key>: <value>.
|
|
107
|
+
frontmatter_files_with() {
|
|
108
|
+
local dir="$1" key="$2" want="$3" f
|
|
109
|
+
while IFS= read -r f; do
|
|
110
|
+
[[ -n "$f" ]] || continue
|
|
111
|
+
[[ "$(frontmatter_field "$f" "$key")" == "$want" ]] && echo "$f"
|
|
112
|
+
done < <(find "$dir" -type f -name '*.md' 2>/dev/null)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Counts mandatory tasks under a plan that are not yet done.
|
|
116
|
+
#
|
|
117
|
+
# The two closing tasks carry `mandatory: true`. A plan cannot be COMPLETE while one
|
|
118
|
+
# of them is unfinished, however the boxes happen to add up — that gate was stated in
|
|
119
|
+
# the templates and enforced nowhere (issue #12).
|
|
120
|
+
#
|
|
121
|
+
# Additive by design: a plan with no `mandatory: true` artifacts behaves exactly as it
|
|
122
|
+
# did before. Retroactively reopening finished plans would be worse than the bug.
|
|
123
|
+
unfinished_mandatory() {
|
|
124
|
+
local n=0 f st
|
|
125
|
+
while IFS= read -r f; do
|
|
126
|
+
[[ -n "$f" ]] || continue
|
|
127
|
+
st=$(frontmatter_status "$f")
|
|
128
|
+
[[ "$st" == "done" || "$st" == "archived" ]] || n=$((n + 1))
|
|
129
|
+
done < <(frontmatter_files_with "$1" "mandatory" "true")
|
|
130
|
+
echo "$n"
|
|
131
|
+
}
|
|
132
|
+
|
|
64
133
|
COMPLETE=()
|
|
65
134
|
ROWS=()
|
|
66
135
|
|
|
@@ -74,13 +143,19 @@ for plan_path in "$PLANNING"/*/; do
|
|
|
74
143
|
|
|
75
144
|
read -r total checked <<<"$(count_boxes "${plan_path%/}")"
|
|
76
145
|
status=$(frontmatter_status "$root")
|
|
77
|
-
blocked=$(
|
|
146
|
+
blocked=$(frontmatter_files_with "${plan_path%/}" "status" "blocked" | wc -l | tr -d ' ')
|
|
147
|
+
pending_mandatory=$(unfinished_mandatory "${plan_path%/}")
|
|
78
148
|
|
|
79
149
|
if [[ "$status" == "archived" ]]; then
|
|
80
150
|
verdict="archived"
|
|
81
151
|
elif [[ "$status" == "done" ]] || { [[ "$total" -gt 0 ]] && [[ "$checked" -eq "$total" ]]; }; then
|
|
82
|
-
|
|
83
|
-
|
|
152
|
+
if [[ "$pending_mandatory" -gt 0 ]]; then
|
|
153
|
+
# Every box ticked, but a mandatory closer is not done. Not complete.
|
|
154
|
+
verdict="in progress"
|
|
155
|
+
else
|
|
156
|
+
verdict="COMPLETE"
|
|
157
|
+
COMPLETE+=("$slug")
|
|
158
|
+
fi
|
|
84
159
|
elif [[ "$blocked" -gt 0 ]]; then
|
|
85
160
|
verdict="blocked"
|
|
86
161
|
elif [[ "$total" -eq 0 ]]; then
|
|
@@ -161,7 +161,7 @@ Create this file FIRST for any complex task:
|
|
|
161
161
|
- [ ] Phase 3: Execute/build
|
|
162
162
|
- [ ] Phase 4: Review and deliver
|
|
163
163
|
- [ ] Phase 5: Validate success through comprehensive testing (MANDATORY)
|
|
164
|
-
- [ ] Phase 6: Documentation pass -- create/update/deprecate docs (MANDATORY)
|
|
164
|
+
- [ ] Phase 6: Documentation pass -- create/update/deprecate as many docs as needed to capture what was done, where it lives, how to troubleshoot it, etc. etc.. etc.. (MANDATORY)
|
|
165
165
|
|
|
166
166
|
> Phases 5 and 6 are mandatory and MUST stay the last two phases of this plan.
|
|
167
167
|
> Insert new work above them; never after. Renumber them if you add phases.
|
|
@@ -169,7 +169,10 @@ Create this file FIRST for any complex task:
|
|
|
169
169
|
> - **Validate success**: prove the work with tests that fail if the change breaks.
|
|
170
170
|
> - **Documentation pass**: create/update/deprecate every doc this change touches --
|
|
171
171
|
> what it is, where it lives, how to fix it, how to operate it, why it matters.
|
|
172
|
-
> If hit-em-with-the-docs is installed, use it for this pass
|
|
172
|
+
> If hit-em-with-the-docs is installed, you MUST use it for this pass --
|
|
173
|
+
> `npx hewtd integrate <file> -a` to create, `npx hewtd archive <file>` to retire,
|
|
174
|
+
> `npx hewtd maintain --quick` to regenerate indexes. Never hand-edit INDEX.md or
|
|
175
|
+
> REGISTRY.md; they are generated.
|
|
173
176
|
|
|
174
177
|
## Key Questions
|
|
175
178
|
1. [Question to answer]
|
package/templates/lg/phase.md
CHANGED
|
@@ -20,9 +20,9 @@ Each task is a bounded deliverable. Tasks default to dependencies-first scheduli
|
|
|
20
20
|
Tasks with no inter-dependencies can be marked `parallelizable: true` in their frontmatter
|
|
21
21
|
so subagent teams can pick them up concurrently.]
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
- [ ] **Validate success through comprehensive testing** (MANDATORY — second-to-last)
|
|
25
|
-
- [ ] **Documentation pass — create/update/deprecate docs
|
|
23
|
+
_(no tasks yet — run `/start-task "Task name" --parent PHASE_SLUG_PLACEHOLDER`)_
|
|
24
|
+
- [ ] **Validate success through comprehensive testing** (`validate-success-through-comprehensive-testing`) (MANDATORY — second-to-last)
|
|
25
|
+
- [ ] **Documentation pass — create/update/deprecate as many docs as needed to capture what was done, where it lives, how to troubleshoot it, etc. etc.. etc..** (`documentation-pass-create-update-deprecate-docs`) (MANDATORY — last)
|
|
26
26
|
|
|
27
27
|
> The two mandatory closing tasks above MUST remain the last two tasks of this phase.
|
|
28
28
|
> Add new tasks above them; never after. A phase cannot be marked `done` until both are
|
|
@@ -31,7 +31,10 @@ so subagent teams can pick them up concurrently.]
|
|
|
31
31
|
> - **Validate success**: prove the phase's work with tests that fail if it breaks.
|
|
32
32
|
> - **Documentation pass**: create/update/deprecate every doc this phase touches —
|
|
33
33
|
> what it is, where it lives, how to fix it, how to operate it, why it matters.
|
|
34
|
-
> If hit-em-with-the-docs is installed, use it for this pass
|
|
34
|
+
> If hit-em-with-the-docs is installed, you MUST use it for this pass --
|
|
35
|
+
> `npx hewtd integrate <file> -a` to create, `npx hewtd archive <file>` to retire,
|
|
36
|
+
> `npx hewtd maintain --quick` to regenerate indexes. Never hand-edit INDEX.md or
|
|
37
|
+
> REGISTRY.md; they are generated.
|
|
35
38
|
|
|
36
39
|
## Decisions Made
|
|
37
40
|
[Phase-level decisions that affect multiple tasks. Format: `**Decision**: rationale`.]
|
package/templates/lg/task.md
CHANGED
|
@@ -9,6 +9,7 @@ plan_kind: task
|
|
|
9
9
|
parent: PHASE_SLUG_PLACEHOLDER
|
|
10
10
|
depends_on: []
|
|
11
11
|
parallelizable: false
|
|
12
|
+
mandatory: false
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
# Task: TASK_TITLE_PLACEHOLDER
|
|
@@ -29,8 +30,8 @@ under `atoms/` that subagents read via the planning MCP.
|
|
|
29
30
|
Inline checkbox vs. standalone atom file — see anti-pattern doc:
|
|
30
31
|
.documentation/standards/atom-granularity.md.]
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
_(no atoms yet — add inline checkboxes here for simple atoms,
|
|
34
|
+
or run `/start-atom "Atom name" --parent TASK_SLUG_PLACEHOLDER` for complex ones)_
|
|
34
35
|
|
|
35
36
|
## Decisions Made
|
|
36
37
|
[Task-level decisions. Format: `**Decision**: rationale`.]
|