@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.
Files changed (63) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.documentation/INDEX.md +15 -7
  4. package/.documentation/REGISTRY.md +2 -2
  5. package/.documentation/agents/INDEX.md +2 -2
  6. package/.documentation/agents/REGISTRY.md +2 -2
  7. package/.documentation/api/INDEX.md +2 -2
  8. package/.documentation/api/REGISTRY.md +2 -2
  9. package/.documentation/architecture/INDEX.md +5 -3
  10. package/.documentation/architecture/REGISTRY.md +8 -4
  11. package/.documentation/architecture/lg-mode.md +16 -0
  12. package/.documentation/architecture/lg-plan-artifact-lifecycle.md +104 -0
  13. package/.documentation/architecture/skill-linking-and-reclaim.md +84 -0
  14. package/.documentation/backups/INDEX.md +2 -2
  15. package/.documentation/backups/REGISTRY.md +2 -2
  16. package/.documentation/database/INDEX.md +2 -2
  17. package/.documentation/database/REGISTRY.md +2 -2
  18. package/.documentation/devops/INDEX.md +2 -2
  19. package/.documentation/devops/REGISTRY.md +2 -2
  20. package/.documentation/features/INDEX.md +2 -2
  21. package/.documentation/features/REGISTRY.md +2 -2
  22. package/.documentation/plans/INDEX.md +2 -2
  23. package/.documentation/plans/REGISTRY.md +2 -2
  24. package/.documentation/procedures/INDEX.md +4 -3
  25. package/.documentation/procedures/REGISTRY.md +6 -4
  26. package/.documentation/procedures/plan-completion-and-archive.md +34 -0
  27. package/.documentation/procedures/plugin-update-delivery.md +106 -0
  28. package/.documentation/quickstart/INDEX.md +2 -2
  29. package/.documentation/quickstart/REGISTRY.md +2 -2
  30. package/.documentation/reference/INDEX.md +10 -3
  31. package/.documentation/reference/REGISTRY.md +8 -4
  32. package/.documentation/reference/mandatory-frontmatter.md +71 -0
  33. package/.documentation/reference/skill-version-marker.md +70 -0
  34. package/.documentation/security/INDEX.md +2 -2
  35. package/.documentation/security/REGISTRY.md +2 -2
  36. package/.documentation/standards/INDEX.md +2 -2
  37. package/.documentation/standards/REGISTRY.md +2 -2
  38. package/.documentation/standards/mandatory-closing-phases.md +21 -1
  39. package/.documentation/testing/INDEX.md +2 -2
  40. package/.documentation/testing/REGISTRY.md +2 -2
  41. package/.documentation/testing/test-suite.md +38 -0
  42. package/.documentation/troubleshooting/INDEX.md +8 -8
  43. package/.documentation/troubleshooting/REGISTRY.md +20 -5
  44. package/.documentation/troubleshooting/plan-never-reads-complete.md +109 -0
  45. package/.documentation/troubleshooting/stale-skill-dir-drift.md +115 -0
  46. package/.documentation/workflows/INDEX.md +2 -2
  47. package/.documentation/workflows/REGISTRY.md +2 -2
  48. package/CHANGELOG.md +176 -0
  49. package/bin/persistent-planning.js +56 -6
  50. package/hooks/session-start.js +157 -15
  51. package/package.json +1 -1
  52. package/scripts/archive-plan.sh +3 -6
  53. package/scripts/init-atom.sh +7 -0
  54. package/scripts/init-phase.sh +58 -6
  55. package/scripts/init-planning.sh +18 -6
  56. package/scripts/init-task.sh +7 -0
  57. package/scripts/lib/planning.sh +122 -0
  58. package/scripts/lib/skill-link.js +177 -0
  59. package/scripts/link-skills.js +38 -0
  60. package/scripts/plan-status.sh +85 -10
  61. package/skills/persistent-planning/SKILL.md +5 -2
  62. package/templates/lg/phase.md +7 -4
  63. package/templates/lg/task.md +3 -2
@@ -0,0 +1,177 @@
1
+ // Skill-dir reclaim + drift detection.
2
+ //
3
+ // Why this exists: v1's `persistent-planning install` (removed in v2.0.0) COPIED
4
+ // skills/persistent-planning/ into a consumer's .claude/skills/. The v2+ linker in
5
+ // claude-plugin-runtime only symlinks, and when it finds a real directory at the
6
+ // destination it warns and skips — forever. So v1 leftovers keep executing a frozen
7
+ // copy while the update check reports the (correct, current) plugin version. Two
8
+ // artifacts, nothing reconciling them.
9
+ //
10
+ // The fix is to reclaim the real directory BEFORE the runtime's linker runs: rename
11
+ // it aside, which leaves a clean destination the linker then symlinks normally. That
12
+ // needs no upstream change — see the phase notes for why this lives here for now.
13
+ //
14
+ // Nothing here ever deletes. A reclaimed directory is renamed to
15
+ // <name>.bak-<ISO8601>, and a failure to rename falls through to the old skip.
16
+
17
+ import { existsSync, lstatSync, readdirSync, readFileSync, readlinkSync, renameSync, writeFileSync } from "node:fs";
18
+ import { join, relative, resolve, dirname } from "node:path";
19
+
20
+ export const ENV_PREFIX = "PERSISTENT_PLANNING";
21
+
22
+ function lstatSafe(p) {
23
+ try { return lstatSync(p); } catch { return null; }
24
+ }
25
+
26
+ /** Skill directory names shipped by this package. Mirrors the runtime's filter. */
27
+ export function packagedSkillNames(packageRoot, skillsSubdir = "skills") {
28
+ const dir = join(packageRoot, skillsSubdir);
29
+ if (!existsSync(dir)) return [];
30
+ try {
31
+ return readdirSync(dir, { withFileTypes: true })
32
+ .filter((d) => d.isDirectory() && !d.name.endsWith("-workspace"))
33
+ .map((d) => d.name);
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+
39
+ /**
40
+ * What is actually sitting at .claude/skills/<name>, and is it ours?
41
+ *
42
+ * Returns one of:
43
+ * absent — nothing there; the linker will create the symlink
44
+ * symlink-ok — symlink pointing at this package's copy; healthy
45
+ * symlink-foreign — symlink pointing somewhere else; another install owns it
46
+ * real-dir — a real directory; v1 debris or a hand-managed copy. This is
47
+ * the drift state: nothing in the current system creates one.
48
+ */
49
+ export function describeSkill(consumerRoot, packageRoot, name, skillsSubdir = "skills") {
50
+ const dest = join(consumerRoot, ".claude", "skills", name);
51
+ const src = join(packageRoot, skillsSubdir, name);
52
+ const st = lstatSafe(dest);
53
+ if (!st) return { name, dest, src, state: "absent" };
54
+ if (st.isSymbolicLink()) {
55
+ let target = null;
56
+ try { target = readlinkSync(dest); } catch { /* unreadable link */ }
57
+ const expected = relative(dirname(dest), src);
58
+ if (target === expected) return { name, dest, src, state: "symlink-ok", target };
59
+ // A different-but-equivalent path still resolves to us; treat that as healthy.
60
+ try {
61
+ if (target && resolve(dirname(dest), target) === resolve(src)) {
62
+ return { name, dest, src, state: "symlink-ok", target };
63
+ }
64
+ } catch { /* fall through */ }
65
+ return { name, dest, src, state: "symlink-foreign", target };
66
+ }
67
+ return { name, dest, src, state: "real-dir" };
68
+ }
69
+
70
+ /** Every packaged skill's current state in a consumer repo. */
71
+ export function surveySkills(consumerRoot, packageRoot, skillsSubdir = "skills") {
72
+ return packagedSkillNames(packageRoot, skillsSubdir)
73
+ .map((n) => describeSkill(consumerRoot, packageRoot, n, skillsSubdir));
74
+ }
75
+
76
+ function backupPath(dest) {
77
+ // ISO8601, filesystem-safe. Collisions only within the same second on the same
78
+ // dir, which a suffix resolves.
79
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
80
+ let candidate = `${dest}.bak-${stamp}`;
81
+ let n = 1;
82
+ while (existsSync(candidate)) candidate = `${dest}.bak-${stamp}-${n++}`;
83
+ return candidate;
84
+ }
85
+
86
+ /**
87
+ * Rename any real directory at .claude/skills/<name> aside so the linker can symlink.
88
+ *
89
+ * Idempotent: a healthy symlink is left completely alone, so a second run is a no-op
90
+ * and produces no second backup.
91
+ *
92
+ * Returns { reclaimed: [{name, from, to}], failed: [{name, dest, error}], healthy: n }.
93
+ */
94
+ export function reclaimStaleSkillDirs(consumerRoot, packageRoot, opts = {}) {
95
+ const { skillsSubdir = "skills", log = () => {} } = opts;
96
+ const result = { reclaimed: [], failed: [], healthy: 0 };
97
+
98
+ if (process.env[`${ENV_PREFIX}_NO_RECLAIM`] === "1") {
99
+ log(`[persistent-planning] ${ENV_PREFIX}_NO_RECLAIM=1 — leaving existing skill dirs alone.`);
100
+ return result;
101
+ }
102
+
103
+ for (const info of surveySkills(consumerRoot, packageRoot, skillsSubdir)) {
104
+ if (info.state !== "real-dir") {
105
+ if (info.state === "symlink-ok") result.healthy++;
106
+ continue;
107
+ }
108
+ const to = backupPath(info.dest);
109
+ try {
110
+ renameSync(info.dest, to);
111
+ result.reclaimed.push({ name: info.name, from: info.dest, to });
112
+ log(`[persistent-planning] reclaimed stale skill dir ${info.dest} -> ${to}`);
113
+ } catch (err) {
114
+ // ponytail: never rm -rf a directory a user may have hand-edited. If we cannot
115
+ // move it, say so loudly and leave it — the old skip, but no longer silent.
116
+ result.failed.push({ name: info.name, dest: info.dest, error: err?.message || String(err) });
117
+ log(
118
+ `[persistent-planning] WARNING: ${info.dest} is a real directory, not a symlink,\n` +
119
+ ` so it will keep running a frozen copy of the skill. Could not move it aside:\n` +
120
+ ` ${err?.message || err}\n` +
121
+ ` Move or remove it, then run: npx persistent-planning relink`
122
+ );
123
+ }
124
+ }
125
+ return result;
126
+ }
127
+
128
+ /** Skills still drifted after a reclaim attempt — what SessionStart should warn about. */
129
+ export function driftedSkills(consumerRoot, packageRoot, skillsSubdir = "skills") {
130
+ return surveySkills(consumerRoot, packageRoot, skillsSubdir)
131
+ .filter((s) => s.state === "real-dir" || s.state === "symlink-foreign");
132
+ }
133
+
134
+ // ── Version marker ──────────────────────────────────────────────────────────
135
+ //
136
+ // The drift that started issue #10 was invisible because the update check read the
137
+ // plugin's version while /start-planning executed the skill dir's copy — same number,
138
+ // different artifact. The primary signal is structural (describeSkill: a real dir is
139
+ // always suspect, because nothing in the current system creates one). This marker is
140
+ // the secondary signal: it says how far behind a drifted copy actually is, and it is
141
+ // the only signal available on install shapes where a symlink is impossible.
142
+
143
+ export const MARKER = ".version";
144
+
145
+ /** Version of the skill copy that would actually execute, or null. */
146
+ export function readSkillVersion(consumerRoot, name) {
147
+ try {
148
+ const raw = readFileSync(join(consumerRoot, ".claude", "skills", name, MARKER), "utf8");
149
+ return raw.trim() || null;
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Stamp linked skills with the version they came from.
157
+ *
158
+ * Only ever stamps a healthy symlink — never a real directory. Stamping a real dir
159
+ * would mark stale code as current and defeat the whole check. Best-effort: a
160
+ * read-only package dir (npm cache, plugin cache) just leaves the marker absent,
161
+ * which the drift check treats as "unknown", not "fine".
162
+ */
163
+ export function writeVersionMarkers(consumerRoot, packageRoot, version, opts = {}) {
164
+ const { skillsSubdir = "skills" } = opts;
165
+ if (!version) return { stamped: [], skipped: [] };
166
+ const out = { stamped: [], skipped: [] };
167
+ for (const info of surveySkills(consumerRoot, packageRoot, skillsSubdir)) {
168
+ if (info.state !== "symlink-ok") { out.skipped.push(info.name); continue; }
169
+ try {
170
+ writeFileSync(join(info.dest, MARKER), `${version}\n`, "utf8");
171
+ out.stamped.push(info.name);
172
+ } catch {
173
+ out.skipped.push(info.name);
174
+ }
175
+ }
176
+ return out;
177
+ }
@@ -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
+ }
@@ -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
- [[ -f "$1" ]] || return
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
- total=$(grep -rhoE '^[[:space:]]*- \[[ xX]\] ' --include='*.md' "$1" 2>/dev/null | wc -l | tr -d ' ')
60
- checked=$(grep -rhoE '^[[:space:]]*- \[[xX]\] ' --include='*.md' "$1" 2>/dev/null | wc -l | tr -d ' ')
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=$(grep -rl '^status: blocked' --include='*.md' "${plan_path%/}" 2>/dev/null | wc -l | tr -d ' ')
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
- verdict="COMPLETE"
83
- COMPLETE+=("$slug")
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]
@@ -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
- - [ ] (no tasks yet — run `/start-task "Task name" --parent PHASE_SLUG_PLACEHOLDER`)
24
- - [ ] **Validate success through comprehensive testing** (MANDATORY — second-to-last)
25
- - [ ] **Documentation pass — create/update/deprecate docs** (MANDATORY — last)
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`.]
@@ -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
- - [ ] (no atoms yet — add inline checkboxes here for simple atoms,
33
- or run `/start-atom "Atom name" --parent TASK_SLUG_PLACEHOLDER` for complex ones)
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`.]