@theglitchking/persistent-planning 3.1.0 → 3.4.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/.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 +106 -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 +176 -0
- package/bin/persistent-planning.js +56 -6
- package/hooks/session-start.js +157 -15
- 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
|
@@ -3,7 +3,7 @@ title: Troubleshooting Registry
|
|
|
3
3
|
tier: reference
|
|
4
4
|
domains: [troubleshooting]
|
|
5
5
|
status: active
|
|
6
|
-
last_updated: '2026-
|
|
6
|
+
last_updated: '2026-09-04'
|
|
7
7
|
version: '1.0.0'
|
|
8
8
|
purpose: Quick reference for troubleshooting documentation
|
|
9
9
|
---
|
|
@@ -16,13 +16,28 @@ purpose: Quick reference for troubleshooting documentation
|
|
|
16
16
|
|
|
17
17
|
| Metric | Value |
|
|
18
18
|
|--------|-------|
|
|
19
|
-
| Documents |
|
|
19
|
+
| Documents | 2 |
|
|
20
20
|
| Category | advanced |
|
|
21
21
|
| Load Priority | 6/10 |
|
|
22
22
|
|
|
23
|
-
## Documents
|
|
23
|
+
## Active Documents
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
- [A plan won't read COMPLETE](plan-never-reads-complete.md)
|
|
26
|
+
- [Stale skill directory — commands run frozen code](stale-skill-dir-drift.md)
|
|
27
|
+
|
|
28
|
+
## Recently Updated
|
|
29
|
+
|
|
30
|
+
- [A plan won't read COMPLETE](plan-never-reads-complete.md) - 2026-09-04
|
|
31
|
+
- [Stale skill directory — commands run frozen code](stale-skill-dir-drift.md) - 2026-09-04
|
|
32
|
+
|
|
33
|
+
## By Status
|
|
34
|
+
|
|
35
|
+
| Status | Count |
|
|
36
|
+
|--------|-------|
|
|
37
|
+
| active | 2 |
|
|
38
|
+
| draft | 0 |
|
|
39
|
+
| deprecated | 0 |
|
|
40
|
+
| archived | 0 |
|
|
26
41
|
|
|
27
42
|
## Keywords
|
|
28
43
|
|
|
@@ -35,4 +50,4 @@ purpose: Quick reference for troubleshooting documentation
|
|
|
35
50
|
|
|
36
51
|
---
|
|
37
52
|
|
|
38
|
-
*Last updated: 2026-
|
|
53
|
+
*Last updated: 2026-09-04*
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: A plan won't read COMPLETE
|
|
3
|
+
tier: guide
|
|
4
|
+
domains:
|
|
5
|
+
- troubleshooting
|
|
6
|
+
audience:
|
|
7
|
+
- developers
|
|
8
|
+
tags: []
|
|
9
|
+
status: active
|
|
10
|
+
last_updated: '2026-09-04'
|
|
11
|
+
version: 1.0.0
|
|
12
|
+
purpose: >-
|
|
13
|
+
Three reasons a plan will not read COMPLETE — an unfinished mandatory closer, a
|
|
14
|
+
placeholder counted as work, or a blocked artifact — and the fix for each.
|
|
15
|
+
estimated_read_time: 2 minutes
|
|
16
|
+
word_count: 264
|
|
17
|
+
last_validated: '2026-09-04'
|
|
18
|
+
backlinks: []
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# A plan won't read COMPLETE
|
|
22
|
+
|
|
23
|
+
`/plan-status` shows a plan stuck below 100%, or at `100%` but still `in progress`,
|
|
24
|
+
and it is not obvious what is outstanding. There are three causes with three
|
|
25
|
+
different fixes.
|
|
26
|
+
|
|
27
|
+
## 1. Every box is ticked but it still says `in progress`
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
my-phase in progress 13/13 boxes
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
A **mandatory closing task is not done.** Ticking its checkbox in `phase.md` is not
|
|
34
|
+
enough — the closer's own `task.md` must carry `status: done`.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
grep -l '^mandatory: true' .planning/my-phase/*/task.md | xargs grep -H '^status:'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Anything not `done` or `archived` is what is holding the plan open. Finish the work,
|
|
41
|
+
then set `status: done` in that task's frontmatter.
|
|
42
|
+
|
|
43
|
+
This is deliberate. A closer that was ticked but never performed is precisely the
|
|
44
|
+
failure the gate exists to catch — see
|
|
45
|
+
[`mandatory:` frontmatter](../reference/mandatory-frontmatter.md).
|
|
46
|
+
|
|
47
|
+
## 2. The count includes a box that isn't work
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
my-phase in progress 0/5 boxes # but only 2 real tasks exist
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Older plans carry the placeholder lines as **checkboxes**:
|
|
54
|
+
|
|
55
|
+
```markdown
|
|
56
|
+
- [ ] (no tasks yet — run `/start-task …`)
|
|
57
|
+
- [ ] (no atoms yet — add inline checkboxes here for simple atoms,
|
|
58
|
+
or run `/start-atom …` for complex ones)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`plan-status.sh` counts every `- [ ]` under a plan, so those inflate the total and the
|
|
62
|
+
plan cannot finish until someone ticks a line that says nothing exists.
|
|
63
|
+
|
|
64
|
+
**Fix:** delete the placeholder line, or add a real task/atom — from 3.3.0 the init
|
|
65
|
+
scripts remove it automatically on first insert. New plans ship the placeholders as
|
|
66
|
+
italic text, which is not counted.
|
|
67
|
+
|
|
68
|
+
## 2b. Quoted markdown used to count (fixed in 3.3.1)
|
|
69
|
+
|
|
70
|
+
Before 3.3.1, three readers scanned whole files: the checkbox counter, the mandatory
|
|
71
|
+
gate, and blocked detection. A `notes.md` that merely *documented* the contract tripped
|
|
72
|
+
all three — a fenced `- [ ]` example inflated the denominator, a fenced `mandatory: true`
|
|
73
|
+
held the plan open with the box count reading a perfect `n/n`, and a fenced
|
|
74
|
+
`status: blocked` produced a false `blocked` verdict.
|
|
75
|
+
|
|
76
|
+
Frontmatter fields are now read from frontmatter only, and checkbox counting skips fenced
|
|
77
|
+
code blocks. If you are on an older version, move the example out of the plan or upgrade.
|
|
78
|
+
|
|
79
|
+
## 3. Something under the plan is `status: blocked`
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
my-phase blocked 7/20 boxes
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`plan-status.sh` reports `blocked` when any artifact under the plan has
|
|
86
|
+
`status: blocked`. Find it:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
grep -rl '^status: blocked' .planning/my-phase/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
That is a report, not a defect — the plan is telling you what it is waiting on.
|
|
93
|
+
|
|
94
|
+
## Checking the whole picture
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bash scripts/plan-status.sh # table: in progress / blocked / COMPLETE / empty
|
|
98
|
+
bash scripts/plan-status.sh --complete # one complete slug per line, for scripts
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`archive-plan.sh` refuses an incomplete plan using the same check, so anything above
|
|
102
|
+
that blocks COMPLETE also blocks archiving. `--force` overrides it; nothing is ever
|
|
103
|
+
deleted either way.
|
|
104
|
+
|
|
105
|
+
## Related
|
|
106
|
+
|
|
107
|
+
- [Plan completion and archive](../procedures/plan-completion-and-archive.md)
|
|
108
|
+
- [lg plan artifact lifecycle](../architecture/lg-plan-artifact-lifecycle.md)
|
|
109
|
+
- [Mandatory closing phases](../standards/mandatory-closing-phases.md)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Stale skill directory — commands run frozen code
|
|
3
|
+
tier: guide
|
|
4
|
+
domains:
|
|
5
|
+
- troubleshooting
|
|
6
|
+
audience:
|
|
7
|
+
- developers
|
|
8
|
+
tags: []
|
|
9
|
+
status: active
|
|
10
|
+
last_updated: '2026-09-04'
|
|
11
|
+
version: 1.0.0
|
|
12
|
+
purpose: Symptom, cause, detection and repair for a stale .claude/skills/persistent-planning
|
|
13
|
+
directory that makes commands run frozen pre-3.1.0 code (issue #10).
|
|
14
|
+
estimated_read_time: 3 minutes
|
|
15
|
+
word_count: 403
|
|
16
|
+
last_validated: '2026-09-04'
|
|
17
|
+
backlinks: []
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Stale skill directory — commands run frozen code
|
|
21
|
+
|
|
22
|
+
`/start-planning` produces a plan that is missing the mandatory validate and
|
|
23
|
+
documentation phases, and has no `## On Completion` archive block. No warning is
|
|
24
|
+
shown. `npx persistent-planning status` reports the current version. Everything
|
|
25
|
+
exits 0.
|
|
26
|
+
|
|
27
|
+
This is the failure mode from [issue #10](https://github.com/the-glitch-kingdom/persistent-planning/issues/10),
|
|
28
|
+
fixed in 3.2.0.
|
|
29
|
+
|
|
30
|
+
## Symptom
|
|
31
|
+
|
|
32
|
+
A generated `task_plan.md` ends at Phase 4. A correct one ends:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
- [ ] Phase 5: Validate success through comprehensive testing (MANDATORY)
|
|
36
|
+
- [ ] 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)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
and carries an `## On Completion` section naming `/plan-status` and `/archive-plan`.
|
|
40
|
+
|
|
41
|
+
## Cause
|
|
42
|
+
|
|
43
|
+
`.claude/skills/persistent-planning/` is a **real directory** instead of a symlink
|
|
44
|
+
into the installed package. v1's `persistent-planning install` (removed in 2.0.0)
|
|
45
|
+
copied the skill dir. The v2+ linker only symlinks, and when it found a real
|
|
46
|
+
directory at the destination it warned once during `npm install` and skipped —
|
|
47
|
+
permanently.
|
|
48
|
+
|
|
49
|
+
The result: the update check read the *plugin's* version while `/start-planning`
|
|
50
|
+
executed the *skill dir's* frozen copy. Same version number, different artifact,
|
|
51
|
+
nothing reconciling them. `commands/start-planning.md` lists
|
|
52
|
+
`~/.claude/skills/persistent-planning/scripts/init-planning.sh` among its fallback
|
|
53
|
+
paths, and in a v1-era skill dir that file exists — so the stale copy wins.
|
|
54
|
+
|
|
55
|
+
## Detect
|
|
56
|
+
|
|
57
|
+
**Check the shape, not the contents:**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ls -ld .claude/skills/persistent-planning
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A symlink is healthy. A real directory is drift — nothing in the current system
|
|
64
|
+
creates one.
|
|
65
|
+
|
|
66
|
+
**Confirm how far behind, by diff:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
diff <path-to-package>/scripts/init-planning.sh \
|
|
70
|
+
.claude/skills/persistent-planning/scripts/init-planning.sh
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> **Do not verify by keyword grep.** Counting `Phase` or `MANDATORY` occurrences
|
|
74
|
+
> reported the known-stale copies as healthy during triage; only `diff` exposed the
|
|
75
|
+
> 23-line gap. See [Skill version marker](../reference/skill-version-marker.md) for
|
|
76
|
+
> the version signal.
|
|
77
|
+
|
|
78
|
+
From 3.2.0 the SessionStart hook detects this for you and prints the path, the
|
|
79
|
+
running version vs the installed one, and the fix.
|
|
80
|
+
|
|
81
|
+
## Fix
|
|
82
|
+
|
|
83
|
+
On 3.2.0 or later, with `updatePolicy: auto`, the next session repairs it by itself
|
|
84
|
+
— once per plugin version. Otherwise:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx persistent-planning relink
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Either way the original is renamed to `.claude/skills/persistent-planning.bak-<timestamp>`.
|
|
91
|
+
**Nothing is deleted.** Delete the backup yourself once you are satisfied.
|
|
92
|
+
|
|
93
|
+
If the directory is tracked in git — v1's installer sometimes committed it — the
|
|
94
|
+
repair shows up as deleted files plus a new symlink. That is a real commit for that
|
|
95
|
+
repo to take, and a prompt to decide whether a skill dir belongs in version control
|
|
96
|
+
at all.
|
|
97
|
+
|
|
98
|
+
## When repair cannot run
|
|
99
|
+
|
|
100
|
+
`relink` reports a rename failure and leaves the directory alone rather than
|
|
101
|
+
deleting anything. Move it aside by hand and re-run. To manage the directory
|
|
102
|
+
yourself and silence the machinery:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
PERSISTENT_PLANNING_NO_RECLAIM=1 # never reclaim
|
|
106
|
+
PERSISTENT_PLANNING_SKIP_LINK=1 # never link at all
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`updatePolicy: off` also suppresses both the warning and the repair.
|
|
110
|
+
|
|
111
|
+
## Related
|
|
112
|
+
|
|
113
|
+
- [Skill linking and reclaim](../architecture/skill-linking-and-reclaim.md) — how it works
|
|
114
|
+
- [Plugin update delivery](../procedures/plugin-update-delivery.md) — why the hook, not postinstall
|
|
115
|
+
- [Skill version marker](../reference/skill-version-marker.md) — the `.version` contract
|
|
@@ -3,7 +3,7 @@ title: Workflows Documentation Index
|
|
|
3
3
|
tier: reference
|
|
4
4
|
domains: [workflows]
|
|
5
5
|
status: active
|
|
6
|
-
last_updated: '2026-
|
|
6
|
+
last_updated: '2026-09-04'
|
|
7
7
|
version: '1.0.0'
|
|
8
8
|
purpose: Complete listing of workflows documentation
|
|
9
9
|
---
|
|
@@ -40,4 +40,4 @@ This domain covers: workflow, workflows, process, processes, flow, flowchart, se
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
*Last updated: 2026-
|
|
43
|
+
*Last updated: 2026-09-04*
|
|
@@ -3,7 +3,7 @@ title: Workflows Registry
|
|
|
3
3
|
tier: reference
|
|
4
4
|
domains: [workflows]
|
|
5
5
|
status: active
|
|
6
|
-
last_updated: '2026-
|
|
6
|
+
last_updated: '2026-09-04'
|
|
7
7
|
version: '1.0.0'
|
|
8
8
|
purpose: Quick reference for workflows documentation
|
|
9
9
|
---
|
|
@@ -35,4 +35,4 @@ purpose: Quick reference for workflows documentation
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
*Last updated: 2026-
|
|
38
|
+
*Last updated: 2026-09-04*
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,182 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.4.1] - 2026-09-04
|
|
6
|
+
|
|
7
|
+
### Fixed — the SessionStart hook could not survive a missing runtime (#18)
|
|
8
|
+
|
|
9
|
+
`hooks/session-start.js` imported `@theglitchking/claude-plugin-runtime` statically. That
|
|
10
|
+
package resolves out of the shared `~/.claude/plugins/npm-cache/`, which a marketplace
|
|
11
|
+
plugin cache does not carry itself, so when it could not resolve the **entire hook died** —
|
|
12
|
+
`ERR_MODULE_NOT_FOUND` before any of its work ran.
|
|
13
|
+
|
|
14
|
+
That took down the drift warning and `autoRepair` along with the update check: the two
|
|
15
|
+
things #10 built precisely so a fix could reach an install that is already broken. The
|
|
16
|
+
delivery path had a single point of failure the payload was meant to survive.
|
|
17
|
+
|
|
18
|
+
- **The runtime is loaded lazily now**, in a try/catch returning `null`. A resolution
|
|
19
|
+
failure costs only the update check; the completion nudge, drift warning and repair all
|
|
20
|
+
still run, and the hook emits its own valid `SessionStart` payload since nothing else
|
|
21
|
+
will.
|
|
22
|
+
- **A failed repair no longer stamps the version.** `autoRepair` previously stamped
|
|
23
|
+
`repairedSkillsForVersion` regardless of outcome, to avoid retrying every session — which
|
|
24
|
+
turned a *transient* failure into a permanent one. Reproduced: one session without a
|
|
25
|
+
resolvable runtime marked 3.4.0 done, and the repair was then skipped forever even once
|
|
26
|
+
the runtime returned. It stamps on success only; a failure costs one spawn per session
|
|
27
|
+
while genuinely drifted, and the drift warning fires alongside it naming the manual fix.
|
|
28
|
+
|
|
29
|
+
Both defects came from the #10 work and shipped in 3.4.0. Test suite grows to 125
|
|
30
|
+
assertions, including a fixture with no `node_modules` at all.
|
|
31
|
+
|
|
32
|
+
## [3.4.0] - 2026-09-04
|
|
33
|
+
|
|
34
|
+
### Changed — only completed plans are gitignored (#15)
|
|
35
|
+
|
|
36
|
+
Both init scripts blanket-ignored the entire `.planning/` tree, a v1.0.0 behavior
|
|
37
|
+
(`fb35f19`) that 3.1.0 implicitly reversed and never removed. `archive-plan.sh` had
|
|
38
|
+
already decided only completed plans are local history, and four shipped docs —
|
|
39
|
+
`SKILL.md`, `lg-mode.md` (twice), `README.md` — described `.planning/.archive/` as *the*
|
|
40
|
+
gitignored path. The code did the opposite.
|
|
41
|
+
|
|
42
|
+
In lg mode the contradiction was sharpest: that mode is selected because ≥2 git authors
|
|
43
|
+
exist, plans are what subagents read via the planning MCP, and the first thing
|
|
44
|
+
`init-phase.sh` did was guarantee no teammate would ever receive them. The blanket entry
|
|
45
|
+
also suppressed `archive-plan.sh`'s narrow one — its first guard returns early when the
|
|
46
|
+
whole tree is ignored — so the narrow entry could never appear in a repo where
|
|
47
|
+
`/start-planning` had run, which is every repo.
|
|
48
|
+
|
|
49
|
+
- **Active plans are tracked; only `.planning/.archive/` is ignored.** One rule, shared by
|
|
50
|
+
both lg init and `archive-plan.sh` via `planning_ensure_archive_gitignored`;
|
|
51
|
+
`init-planning.sh` keeps its own copy since it is deliberately self-contained.
|
|
52
|
+
- **An existing blanket `.planning/` line is never rewritten.** It is the user's file. The
|
|
53
|
+
tool says once how to narrow it and moves on — otherwise the plans someone is about to
|
|
54
|
+
create are invisible to their team and nothing tells them.
|
|
55
|
+
- **Init still never creates a `.gitignore`** where none exists. `archive-plan.sh` does,
|
|
56
|
+
because it has just moved files into `.archive/` and that path must be ignored for the
|
|
57
|
+
move to mean anything.
|
|
58
|
+
|
|
59
|
+
sm plan output remains byte-identical. Test suite grows to 119 assertions.
|
|
60
|
+
|
|
61
|
+
## [3.3.1] - 2026-09-04
|
|
62
|
+
|
|
63
|
+
### Fixed — a plan that documented markdown could not complete (#14)
|
|
64
|
+
|
|
65
|
+
`plan-status.sh` had one correct frontmatter reader and three that scanned whole files:
|
|
66
|
+
the checkbox counter, the mandatory gate, and blocked detection. A `notes.md` that merely
|
|
67
|
+
*documented* the contract — which is what notes.md is for — tripped all three:
|
|
68
|
+
|
|
69
|
+
- a fenced `- [ ]` example inflated the denominator, so the plan could never reach
|
|
70
|
+
COMPLETE. A quoted `- [x]` cancelled out; a quoted `- [ ]` did not.
|
|
71
|
+
- a fenced `mandatory: true` held the plan open with the box count showing a perfect
|
|
72
|
+
`1/1` — **no visible signal at all**, and the documented triage steps pointed at a
|
|
73
|
+
notes file that was pure prose. Regression from #13.
|
|
74
|
+
- a fenced `status: blocked` produced a false `blocked` verdict, masked whenever the plan
|
|
75
|
+
was otherwise COMPLETE, so it surfaced only intermittently.
|
|
76
|
+
|
|
77
|
+
`archive-plan.sh` inherited all three through its pre-flight and refused to archive,
|
|
78
|
+
advising `--force` — wrong advice for a miscount.
|
|
79
|
+
|
|
80
|
+
The fix splits in two, because these are not the same defect:
|
|
81
|
+
|
|
82
|
+
- **Checkboxes are body content**, so counting is now fence-aware — backtick and tilde
|
|
83
|
+
fences, tagged or not, indented, closing only on their own character. An unterminated
|
|
84
|
+
fence swallows the rest of the file: ambiguous content is not counted as outstanding.
|
|
85
|
+
- **`status:` and `mandatory:` are frontmatter fields**, so they are read from the leading
|
|
86
|
+
`---` block via the existing scoped reader, generalised as `frontmatter_field`. That is
|
|
87
|
+
strictly stronger than fence-stripping — it also rejects the field appearing in prose or
|
|
88
|
+
a table.
|
|
89
|
+
|
|
90
|
+
All consumers — `archive-plan.sh`, `/plan-status`, the SessionStart hook — shell out to
|
|
91
|
+
`plan-status.sh` and needed no change. Test suite grows to 109 assertions.
|
|
92
|
+
|
|
93
|
+
## [3.3.0] - 2026-09-04
|
|
94
|
+
|
|
95
|
+
### Fixed — lg task lists were maintained by hand, and the mandatory closers were unenforceable (#12)
|
|
96
|
+
|
|
97
|
+
`init-task.sh` never wrote to `phase.md` — it referenced the file only in an existence
|
|
98
|
+
check — and `init-atom.sh` never wrote the atom into its task. So **every lg task list was
|
|
99
|
+
maintained by hand**: create six task directories and the phase still reported
|
|
100
|
+
`(no tasks yet)`. The "closers must stay last" rule therefore governed a list no tool
|
|
101
|
+
produced, and the test that claimed to cover it only ever inspected a freshly rendered
|
|
102
|
+
template, which can never be wrong.
|
|
103
|
+
|
|
104
|
+
- **Creating a child writes it into its parent's list.** `planning_insert_list_item` in
|
|
105
|
+
`scripts/lib/planning.sh` inserts above the first `MANDATORY` entry — so the closers stay
|
|
106
|
+
last *by construction* — appends after the last item in sections with no MANDATORY
|
|
107
|
+
entries, drops the placeholder on first use, is idempotent under `PLANNING_FORCE=1`, and
|
|
108
|
+
is section-scoped. It anchors on the MANDATORY marker, never on line numbers, because the
|
|
109
|
+
list is a human-editable surface.
|
|
110
|
+
- **The closers are real task directories now.** `init-phase.sh` scaffolds
|
|
111
|
+
`validate-success-through-comprehensive-testing/` and
|
|
112
|
+
`documentation-pass-create-update-deprecate-docs/` with `task.md` (`mandatory: true`,
|
|
113
|
+
`parallelizable: false`), `notes.md`, `atoms/`, and seeded atoms. Previously the two most
|
|
114
|
+
important tasks in every plan were the only ones with no artifact a subagent could read.
|
|
115
|
+
- **Placeholders stopped counting as work.** `(no tasks yet …)` and `(no atoms yet …)`
|
|
116
|
+
shipped as real checkboxes, and `plan-status.sh` counts every box under a plan — a fresh
|
|
117
|
+
phase with two tasks reported `0/5`, a plan could reach 100% by ticking lines asserting
|
|
118
|
+
nothing existed, and a finished phase stayed open until someone ticked "no tasks yet".
|
|
119
|
+
They are italic text now; `plan-status.sh` needed no change.
|
|
120
|
+
- **The completion gate is real.** `plan-status.sh` refuses `COMPLETE` while any
|
|
121
|
+
`mandatory: true` task's own `status:` is not `done`. Ticking its checkbox is not enough.
|
|
122
|
+
`archive-plan.sh` inherits this through its existing pre-flight rather than duplicating
|
|
123
|
+
the rule. Strictly additive: plans with no `mandatory:` frontmatter behave exactly as
|
|
124
|
+
before.
|
|
125
|
+
|
|
126
|
+
Test suite grows to 99 assertions, every new one mutating the plan before asserting.
|
|
127
|
+
sm mode verified byte-identical against the pre-change render. New docs:
|
|
128
|
+
`.documentation/architecture/lg-plan-artifact-lifecycle.md`,
|
|
129
|
+
`.documentation/reference/mandatory-frontmatter.md`,
|
|
130
|
+
`.documentation/troubleshooting/plan-never-reads-complete.md`.
|
|
131
|
+
|
|
132
|
+
## [3.2.0] - 2026-09-04
|
|
133
|
+
|
|
134
|
+
### Fixed — Stale skill directories run frozen code (#10)
|
|
135
|
+
|
|
136
|
+
A consumer repo could run a **pre-3.1.0 copy** of the skill indefinitely while the update
|
|
137
|
+
check reported the current version — no nudge, no warning, exit 0 everywhere. Plans
|
|
138
|
+
generated that way silently omitted the mandatory validate and documentation phases and
|
|
139
|
+
the entire `## On Completion` archive block: precisely the guarantees 3.1.0 was released
|
|
140
|
+
to make.
|
|
141
|
+
|
|
142
|
+
**Cause.** v1's `persistent-planning install` (removed in 2.0.0) *copied*
|
|
143
|
+
`skills/persistent-planning/` into `.claude/skills/`. The v2+ linker only symlinks, and
|
|
144
|
+
when it finds a real directory at the destination it warns and skips — permanently. So
|
|
145
|
+
`installedVersion()` reported the plugin's version while `/start-planning` executed the
|
|
146
|
+
skill dir's frozen copy. Two artifacts, nothing reconciling them. (The linker was never
|
|
147
|
+
copying, as first reported; it was refusing to reclaim what v1 left behind.)
|
|
148
|
+
|
|
149
|
+
- **Reclaim instead of skip** — `scripts/lib/skill-link.js` renames a real directory to
|
|
150
|
+
`<name>.bak-<ISO8601>` before the runtime's linker runs, leaving a clean destination it
|
|
151
|
+
then symlinks normally. Nothing is ever deleted; a failed rename falls through to the
|
|
152
|
+
old skip but says so loudly and names the fix. Idempotent — a healthy symlink is left
|
|
153
|
+
alone, so a second run makes no second backup. Opt out with
|
|
154
|
+
`PERSISTENT_PLANNING_NO_RECLAIM=1`.
|
|
155
|
+
- **The drift is detectable now.** The primary signal is structural: a real directory at
|
|
156
|
+
`.claude/skills/<name>` is *always* drift, because nothing in the current system creates
|
|
157
|
+
one. A `.version` marker beside `SKILL.md` is the secondary signal — how far behind a
|
|
158
|
+
drifted copy is, and the only signal available where symlinks are impossible. Only
|
|
159
|
+
healthy symlinks are stamped; stamping a stale copy would mark it current.
|
|
160
|
+
- **SessionStart reports it**, naming the path, the running vs installed version, the
|
|
161
|
+
user-visible consequence, and `npx persistent-planning relink`. Merged into the existing
|
|
162
|
+
single JSON response; fails open; silent under `updatePolicy: off`.
|
|
163
|
+
- **SessionStart repairs it under `updatePolicy: auto`**, once per plugin version. This is
|
|
164
|
+
the part that reaches the installs that are actually broken: a marketplace install has
|
|
165
|
+
no `node_modules` in the plugin cache and **never runs npm postinstall**, so a fix
|
|
166
|
+
shipped only through postinstall would reach nobody affected. The hook is the one thing
|
|
167
|
+
that runs every session in that shape. `nudge` warns without touching the filesystem;
|
|
168
|
+
`off` does nothing at all.
|
|
169
|
+
- **No more clean exits for no-ops.** `update` with no managed install exits 1 and lists
|
|
170
|
+
every path probed, instead of printing `(not installed)` twice and exiting 0. `relink`
|
|
171
|
+
gained `CLAUDE_PLUGIN_ROOT/scripts/` and self-package probes — it previously could not
|
|
172
|
+
run at all in a marketplace-only install, the shape that needs it most — and no longer
|
|
173
|
+
swallows the linker's exit code.
|
|
174
|
+
|
|
175
|
+
Test suite grows to 74 assertions, including a marketplace-shaped fixture with no
|
|
176
|
+
`node_modules`. New docs: `.documentation/troubleshooting/stale-skill-dir-drift.md`,
|
|
177
|
+
`.documentation/architecture/skill-linking-and-reclaim.md`,
|
|
178
|
+
`.documentation/reference/skill-version-marker.md`,
|
|
179
|
+
`.documentation/procedures/plugin-update-delivery.md`.
|
|
180
|
+
|
|
5
181
|
## [3.1.0] - 2026-08-31
|
|
6
182
|
|
|
7
183
|
### Added — Status tracking and completion archive (#4)
|
|
@@ -5,25 +5,49 @@ import { registerUpdateCommands } from "@theglitchking/claude-plugin-runtime";
|
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { existsSync } from "node:fs";
|
|
8
|
-
import { join, resolve } from "node:path";
|
|
8
|
+
import { dirname, join, resolve } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
9
10
|
|
|
10
11
|
const require_ = createRequire(import.meta.url);
|
|
11
12
|
const { version } = require_("../package.json");
|
|
12
13
|
|
|
13
14
|
const PKG = "@theglitchking/persistent-planning";
|
|
14
15
|
|
|
16
|
+
// Where the linker can live, most specific first. The third probe matters: in a
|
|
17
|
+
// marketplace-only install there is no node_modules and cwd is the consumer repo,
|
|
18
|
+
// so the first two both miss and relink -- the one command that repairs a stale
|
|
19
|
+
// skill dir -- could not run at all in exactly the shape that needs it.
|
|
20
|
+
function linkerCandidates(cwd) {
|
|
21
|
+
const out = [
|
|
22
|
+
join(cwd, "node_modules", "@theglitchking", "persistent-planning", "scripts", "link-skills.js"),
|
|
23
|
+
resolve(process.cwd(), "scripts", "link-skills.js"),
|
|
24
|
+
];
|
|
25
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
26
|
+
out.push(join(process.env.CLAUDE_PLUGIN_ROOT, "scripts", "link-skills.js"));
|
|
27
|
+
}
|
|
28
|
+
// The package this CLI is itself running from — correct for `npx <pkg> relink`.
|
|
29
|
+
out.push(resolve(dirname(fileURLToPath(import.meta.url)), "..", "scripts", "link-skills.js"));
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
15
33
|
function runRelink(cwd) {
|
|
16
|
-
const
|
|
17
|
-
const script =
|
|
18
|
-
if (!
|
|
19
|
-
|
|
34
|
+
const candidates = linkerCandidates(cwd);
|
|
35
|
+
const script = candidates.find((p) => existsSync(p));
|
|
36
|
+
if (!script) {
|
|
37
|
+
// ponytail: never exit 0 after doing nothing — a clean success for a no-op is
|
|
38
|
+
// how the stale-skill-dir bug stayed invisible for five months (issue #10).
|
|
39
|
+
console.error("relink failed: link-skills.js not found. Looked in:");
|
|
40
|
+
for (const p of candidates) console.error(` ${p}`);
|
|
41
|
+
console.error("\nIs @theglitchking/persistent-planning installed, or CLAUDE_PLUGIN_ROOT set?");
|
|
42
|
+
process.exitCode = 1;
|
|
20
43
|
return;
|
|
21
44
|
}
|
|
22
|
-
spawnSync(process.execPath, [script], {
|
|
45
|
+
const r = spawnSync(process.execPath, [script], {
|
|
23
46
|
cwd,
|
|
24
47
|
env: { ...process.env, INIT_CWD: cwd },
|
|
25
48
|
stdio: "inherit",
|
|
26
49
|
});
|
|
50
|
+
if (r.status !== 0) process.exitCode = r.status ?? 1;
|
|
27
51
|
}
|
|
28
52
|
|
|
29
53
|
program
|
|
@@ -31,6 +55,32 @@ program
|
|
|
31
55
|
.description("Persistent markdown-based planning for Claude Code.")
|
|
32
56
|
.version(version);
|
|
33
57
|
|
|
58
|
+
// The runtime's `update` resolves the installed version, finds none in a
|
|
59
|
+
// marketplace-only install, prints "Current: (not installed) / Now: (not installed)"
|
|
60
|
+
// and exits 0 -- a clean success for a no-op. Fail before it gets that far, and say
|
|
61
|
+
// where we looked. Same defect as runRelink's missing third probe (issue #10, fix #4).
|
|
62
|
+
// Deliberately NOT including the package this CLI runs from: under
|
|
63
|
+
// `npx --no @theglitchking/persistent-planning update` that copy always exists, which
|
|
64
|
+
// is what let the original bug report see a clean exit 0 for a no-op. Only a *managed*
|
|
65
|
+
// install -- a project dependency or a marketplace plugin root -- is updatable.
|
|
66
|
+
function installCandidates(cwd) {
|
|
67
|
+
const out = [join(cwd, "node_modules", "@theglitchking", "persistent-planning", "package.json")];
|
|
68
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) out.push(join(process.env.CLAUDE_PLUGIN_ROOT, "package.json"));
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
program.hook("preAction", (_thisCmd, actionCmd) => {
|
|
73
|
+
if (actionCmd.name() !== "update") return;
|
|
74
|
+
const candidates = installCandidates(process.cwd());
|
|
75
|
+
if (candidates.some((p) => existsSync(p))) return;
|
|
76
|
+
console.error("update failed: no managed install of @theglitchking/persistent-planning found.");
|
|
77
|
+
console.error("Looked in:");
|
|
78
|
+
for (const p of candidates) console.error(` ${p}`);
|
|
79
|
+
console.error("\nInstall it with `npm i -D @theglitchking/persistent-planning`,");
|
|
80
|
+
console.error("or update the marketplace plugin with `/plugin` inside Claude Code.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
34
84
|
registerUpdateCommands(program, {
|
|
35
85
|
packageName: PKG,
|
|
36
86
|
pluginName: "persistent-planning",
|