pan-wizard 3.32.0 → 3.34.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.
Files changed (209) hide show
  1. package/README.md +23 -20
  2. package/agents/pan-conductor.md +18 -17
  3. package/agents/pan-counterfactual.md +2 -2
  4. package/agents/pan-debugger.md +27 -8
  5. package/agents/pan-design-checker.md +1 -1
  6. package/agents/pan-designer.md +2 -2
  7. package/agents/pan-distiller.md +3 -3
  8. package/agents/pan-document_code.md +16 -16
  9. package/agents/pan-executor.md +15 -11
  10. package/agents/pan-experiment-runner.md +15 -17
  11. package/agents/pan-hardener.md +15 -15
  12. package/agents/pan-integration-checker.md +2 -2
  13. package/agents/pan-knowledge.md +2 -2
  14. package/agents/pan-meta-reviewer.md +5 -5
  15. package/agents/pan-optimizer.md +40 -44
  16. package/agents/pan-phase-researcher.md +10 -10
  17. package/agents/pan-plan-checker.md +36 -30
  18. package/agents/pan-planner.md +77 -53
  19. package/agents/pan-previewer.md +1 -1
  20. package/agents/pan-project-researcher.md +21 -78
  21. package/agents/pan-release.md +1 -1
  22. package/agents/pan-research-synthesizer.md +2 -2
  23. package/agents/pan-reviewer.md +24 -2
  24. package/agents/pan-roadmapper.md +20 -62
  25. package/agents/pan-verifier.md +53 -30
  26. package/bin/install-lib.cjs +228 -38
  27. package/bin/install.js +86 -15
  28. package/commands/pan/army.md +16 -12
  29. package/commands/pan/assumptions.md +2 -2
  30. package/commands/pan/audit-deployment.md +11 -12
  31. package/commands/pan/cost.md +8 -4
  32. package/commands/pan/debug.md +10 -7
  33. package/commands/pan/design-phase.md +3 -3
  34. package/commands/pan/discuss-phase.md +2 -0
  35. package/commands/pan/exec-phase.md +21 -37
  36. package/commands/pan/experiment.md +11 -10
  37. package/commands/pan/focus-auto.md +33 -27
  38. package/commands/pan/focus-design.md +9 -6
  39. package/commands/pan/focus-doc-audit.md +4 -3
  40. package/commands/pan/focus-drift-walking.md +4 -3
  41. package/commands/pan/focus-exec.md +7 -7
  42. package/commands/pan/focus-plan.md +12 -11
  43. package/commands/pan/focus-scan.md +7 -10
  44. package/commands/pan/focus-sync.md +3 -2
  45. package/commands/pan/git.md +8 -8
  46. package/commands/pan/health.md +2 -2
  47. package/commands/pan/hud.md +3 -2
  48. package/commands/pan/hygiene.md +14 -7
  49. package/commands/pan/knowledge.md +9 -9
  50. package/commands/pan/learn.md +10 -7
  51. package/commands/pan/links.md +4 -1
  52. package/commands/pan/map-codebase.md +5 -5
  53. package/commands/pan/mcp-bridge.md +18 -18
  54. package/commands/pan/milestone-done.md +7 -5
  55. package/commands/pan/milestone-new.md +1 -1
  56. package/commands/pan/new-project.md +10 -10
  57. package/commands/pan/optimize.md +16 -14
  58. package/commands/pan/patches.md +2 -6
  59. package/commands/pan/pause.md +1 -1
  60. package/commands/pan/phase-tests.md +4 -3
  61. package/commands/pan/plan-phase.md +9 -19
  62. package/commands/pan/preview.md +1 -1
  63. package/commands/pan/profile.md +4 -4
  64. package/commands/pan/quick.md +1 -1
  65. package/commands/pan/remove-phase.md +1 -1
  66. package/commands/pan/report.md +2 -2
  67. package/commands/pan/research-phase.md +14 -15
  68. package/commands/pan/resume.md +9 -11
  69. package/commands/pan/retro.md +5 -3
  70. package/commands/pan/review-deep.md +10 -10
  71. package/commands/pan/settings.md +1 -1
  72. package/commands/pan/todo-add.md +2 -2
  73. package/commands/pan/todo-check.md +2 -2
  74. package/commands/pan/verify-phase.md +0 -11
  75. package/commands/pan/what-if.md +2 -2
  76. package/hooks/dist/pan-context-monitor.js +343 -44
  77. package/hooks/dist/pan-cost-logger.js +30 -0
  78. package/hooks/dist/pan-state-reinject.js +140 -15
  79. package/hooks/dist/pan-statusline.js +68 -22
  80. package/hooks/dist/pan-stop-guard.js +7 -2
  81. package/package.json +1 -1
  82. package/pan-wizard-core/bin/lib/agents-md.cjs +48 -7
  83. package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
  84. package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
  85. package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
  86. package/pan-wizard-core/bin/lib/commands.cjs +126 -5
  87. package/pan-wizard-core/bin/lib/config.cjs +112 -1
  88. package/pan-wizard-core/bin/lib/constants.cjs +29 -15
  89. package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
  90. package/pan-wizard-core/bin/lib/core.cjs +77 -23
  91. package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
  92. package/pan-wizard-core/bin/lib/cost.cjs +149 -10
  93. package/pan-wizard-core/bin/lib/distill.cjs +6 -3
  94. package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
  95. package/pan-wizard-core/bin/lib/findings.cjs +15 -4
  96. package/pan-wizard-core/bin/lib/focus.cjs +13 -4
  97. package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
  98. package/pan-wizard-core/bin/lib/hygiene.cjs +178 -17
  99. package/pan-wizard-core/bin/lib/init.cjs +12 -5
  100. package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
  101. package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
  102. package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
  103. package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
  104. package/pan-wizard-core/bin/lib/memory.cjs +508 -54
  105. package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
  106. package/pan-wizard-core/bin/lib/optimize.cjs +84 -16
  107. package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
  108. package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
  109. package/pan-wizard-core/bin/lib/phase.cjs +67 -15
  110. package/pan-wizard-core/bin/lib/preview.cjs +3 -1
  111. package/pan-wizard-core/bin/lib/review-deep.cjs +2 -2
  112. package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
  113. package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
  114. package/pan-wizard-core/bin/lib/runner.cjs +2 -1
  115. package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
  116. package/pan-wizard-core/bin/lib/state.cjs +47 -31
  117. package/pan-wizard-core/bin/lib/template.cjs +11 -4
  118. package/pan-wizard-core/bin/lib/verdict.cjs +54 -4
  119. package/pan-wizard-core/bin/lib/verify-drift.cjs +14 -5
  120. package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
  121. package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
  122. package/pan-wizard-core/bin/lib/verify-scope.cjs +74 -4
  123. package/pan-wizard-core/bin/lib/verify.cjs +117 -35
  124. package/pan-wizard-core/bin/lib/whatif.cjs +5 -3
  125. package/pan-wizard-core/bin/pan-tools.cjs +111 -17
  126. package/pan-wizard-core/learnings/index.json +5 -5
  127. package/pan-wizard-core/mcp/server.cjs +18 -9
  128. package/pan-wizard-core/opencode/pan-wizard.js +111 -0
  129. package/pan-wizard-core/references/checkpoints.md +7 -5
  130. package/pan-wizard-core/references/continuation-format.md +3 -3
  131. package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
  132. package/pan-wizard-core/references/design-methodology.md +1 -1
  133. package/pan-wizard-core/references/git-integration.md +4 -6
  134. package/pan-wizard-core/references/git-planning-commit.md +2 -2
  135. package/pan-wizard-core/references/guardrails.md +2 -2
  136. package/pan-wizard-core/references/handoff-decisions.md +3 -3
  137. package/pan-wizard-core/references/model-profile-resolution.md +5 -5
  138. package/pan-wizard-core/references/model-profiles.md +14 -9
  139. package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
  140. package/pan-wizard-core/references/planning-config.md +4 -4
  141. package/pan-wizard-core/references/tdd.md +10 -1
  142. package/pan-wizard-core/references/verdict-contract.md +2 -1
  143. package/pan-wizard-core/references/verification-patterns.md +5 -5
  144. package/pan-wizard-core/templates/codebase/architecture.md +2 -2
  145. package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
  146. package/pan-wizard-core/templates/codebase/concerns.md +3 -3
  147. package/pan-wizard-core/templates/codebase/integrations.md +6 -6
  148. package/pan-wizard-core/templates/codebase/relationships.md +2 -2
  149. package/pan-wizard-core/templates/codebase/stack.md +2 -2
  150. package/pan-wizard-core/templates/codebase/structure.md +4 -4
  151. package/pan-wizard-core/templates/codebase/testing.md +4 -4
  152. package/pan-wizard-core/templates/continue-here.md +1 -1
  153. package/pan-wizard-core/templates/debug-subagent-prompt.md +3 -2
  154. package/pan-wizard-core/templates/debug.md +15 -1
  155. package/pan-wizard-core/templates/discovery.md +1 -1
  156. package/pan-wizard-core/templates/milestone-archive.md +6 -6
  157. package/pan-wizard-core/templates/milestone.md +1 -1
  158. package/pan-wizard-core/templates/phase-prompt.md +31 -18
  159. package/pan-wizard-core/templates/planner-subagent-prompt.md +3 -5
  160. package/pan-wizard-core/templates/research-project/architecture.md +3 -3
  161. package/pan-wizard-core/templates/research-project/features.md +1 -1
  162. package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
  163. package/pan-wizard-core/templates/research-project/stack.md +3 -3
  164. package/pan-wizard-core/templates/research.md +5 -5
  165. package/pan-wizard-core/templates/retrospective.md +1 -1
  166. package/pan-wizard-core/templates/roadmap.md +15 -15
  167. package/pan-wizard-core/templates/summary-complex.md +1 -0
  168. package/pan-wizard-core/templates/summary-minimal.md +1 -0
  169. package/pan-wizard-core/templates/summary-standard.md +2 -1
  170. package/pan-wizard-core/templates/summary.md +18 -2
  171. package/pan-wizard-core/templates/uat.md +3 -3
  172. package/pan-wizard-core/templates/user-setup.md +8 -8
  173. package/pan-wizard-core/templates/verification-report.md +54 -4
  174. package/pan-wizard-core/workflows/add-phase.md +1 -1
  175. package/pan-wizard-core/workflows/assumptions.md +1 -1
  176. package/pan-wizard-core/workflows/discuss-phase.md +9 -8
  177. package/pan-wizard-core/workflows/exec-phase.md +34 -60
  178. package/pan-wizard-core/workflows/execute-plan.md +8 -7
  179. package/pan-wizard-core/workflows/health.md +13 -11
  180. package/pan-wizard-core/workflows/help.md +8 -9
  181. package/pan-wizard-core/workflows/learn.md +13 -11
  182. package/pan-wizard-core/workflows/map-codebase.md +10 -10
  183. package/pan-wizard-core/workflows/milestone-audit.md +10 -10
  184. package/pan-wizard-core/workflows/milestone-gaps.md +17 -24
  185. package/pan-wizard-core/workflows/milestone-new.md +7 -6
  186. package/pan-wizard-core/workflows/new-project.md +10 -10
  187. package/pan-wizard-core/workflows/optimize.md +11 -9
  188. package/pan-wizard-core/workflows/pause.md +6 -1
  189. package/pan-wizard-core/workflows/phase-tests.md +4 -4
  190. package/pan-wizard-core/workflows/plan-phase.md +31 -34
  191. package/pan-wizard-core/workflows/profile.md +2 -2
  192. package/pan-wizard-core/workflows/progress.md +5 -7
  193. package/pan-wizard-core/workflows/quick.md +10 -8
  194. package/pan-wizard-core/workflows/remove-phase.md +1 -1
  195. package/pan-wizard-core/workflows/research-phase.md +9 -5
  196. package/pan-wizard-core/workflows/resume-project.md +4 -6
  197. package/pan-wizard-core/workflows/retro.md +3 -1
  198. package/pan-wizard-core/workflows/settings.md +16 -24
  199. package/pan-wizard-core/workflows/todo-check.md +1 -1
  200. package/pan-wizard-core/workflows/transition.md +12 -12
  201. package/pan-wizard-core/workflows/update.md +25 -17
  202. package/pan-wizard-core/workflows/verify-phase.md +24 -17
  203. package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
  204. package/pan-zcode/README.md +7 -7
  205. package/pan-zcode/bin/install-zcode.js +1 -1
  206. package/scripts/build-agent-plugin.js +19 -5
  207. package/scripts/build-plugin.js +3 -1
  208. package/scripts/generate-skills-docs.py +13 -4
  209. package/scripts/test-surface.cjs +4 -0
@@ -18,11 +18,30 @@
18
18
  // `compact` matcher on both; the source is re-checked here so a registration
19
19
  // without the matcher stays inert on every other start.
20
20
  //
21
+ // Gemini CLI and Copilot CLI (market-ideas M33) restart no session after a
22
+ // compaction (Gemini's SessionStart sources: startup | resume | clear; Copilot's:
23
+ // startup | resume | new), and their pre-compaction events cannot add context
24
+ // (Gemini PreCompress and Copilot preCompact are advisory). So there the block
25
+ // arrives in two steps:
26
+ // --mark on PreCompress / preCompact: when a PAN phase is in flight,
27
+ // leave a per-session marker in the per-user hook directory
28
+ // under the OS temp directory (0700, written 0600);
29
+ // --inject <host> on the next tool result (Gemini AfterTool, Copilot
30
+ // postToolUse): consume the marker and return the block once,
31
+ // in that host's output shape. With no marker it prints nothing.
32
+ // A tool result is the first point after a compaction where both hosts take added
33
+ // context, including an autonomous `-p` run that never sees another prompt (read
34
+ // 2026-10-03: gemini-cli v0.61.0 docs/hooks/reference.md; the Copilot hooks
35
+ // reference on docs.github.com). The marker modes need no R39 deferral: in a
36
+ // project with both installs, Copilot also runs Claude's registration of this
37
+ // script, but that one is the SessionStart mode, which a Copilot start never matches.
38
+ //
21
39
  // Inert unless the project is a PAN project with work in flight: `.planning/`
22
40
  // holds a state.md that names a current phase AND a roadmap.md with an unticked
23
41
  // phase. Everywhere else — no planning tree, a focus-only project, a finished
24
- // milestone — it prints nothing. It never writes a file (field finding: hooks
25
- // that scaffolded `.planning/` in projects that never ran PAN).
42
+ // milestone — it prints nothing. It never writes into the project (field finding:
43
+ // hooks that scaffolded `.planning/` in projects that never ran PAN); the only
44
+ // file it writes is the marker, outside the project.
26
45
  //
27
46
  // Fail-open everywhere: bad stdin, unreadable files — exit 0 silently. A missing
28
47
  // reminder costs a re-read; a crashed hook is a failing hook at every start.
@@ -31,7 +50,9 @@
31
50
  // additionalContext; each field is truncated on its own.
32
51
 
33
52
  const fs = require('fs');
53
+ const os = require('os');
34
54
  const path = require('path');
55
+ const crypto = require('crypto');
35
56
 
36
57
  /**
37
58
  * Which planning tree this hook acts on.
@@ -124,7 +145,99 @@ function readIfExists(p) {
124
145
  try { return fs.readFileSync(p, 'utf8'); } catch { return null; }
125
146
  }
126
147
 
148
+ // ─── The marker (Gemini, Copilot) ─────────────────────────────────────────────
149
+
150
+ // A marker older than this belongs to a session that never made another tool call.
151
+ const MARKER_TTL_MS = 6 * 60 * 60 * 1000;
152
+
153
+ /** Gemini sends `session_id`; Copilot's camelCase payloads send `sessionId`. */
154
+ function sessionIdOf(payload) {
155
+ for (const k of ['session_id', 'sessionId']) {
156
+ if (typeof payload[k] === 'string' && payload[k]) return payload[k];
157
+ }
158
+ return '';
159
+ }
160
+
161
+ /** The per-user hook directory's name: the stop guard's and the context monitor's too. */
162
+ function hookDirName() {
163
+ const uid = (typeof process.getuid === 'function' ? process.getuid() : process.env.USERNAME || 'win');
164
+ return `pan-hooks-${uid}`;
165
+ }
166
+
167
+ // Per-user hook state directory inside tmpdir, created 0700 — the same directory
168
+ // and the same checks as hookStateDir() in pan-stop-guard.js (hooks are standalone
169
+ // files and cannot require one another). Null when the directory is not provably
170
+ // ours: on a shared host another user must not be able to plant a marker that
171
+ // injects, or a symlink the marker is written through (M60).
172
+ function hookStateDir() {
173
+ const dir = path.join(os.tmpdir(), hookDirName());
174
+ try {
175
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
176
+ const st = fs.lstatSync(dir);
177
+ if (st.isSymbolicLink()) return null;
178
+ // POSIX-only ownership and mode checks: Windows fakes mode bits (N15).
179
+ if (typeof process.getuid === 'function') {
180
+ if (st.uid !== process.getuid()) return null;
181
+ if ((st.mode & 0o077) !== 0) return null;
182
+ }
183
+ return dir;
184
+ } catch { return null; }
185
+ }
186
+
187
+ /** One marker per (session, project), named by a hash so neither leaks into a file name. Pure. */
188
+ function markerName(sessionId, projectDir) {
189
+ const key = crypto.createHash('sha256').update(`${sessionId}\u0000${path.resolve(projectDir)}`).digest('hex').slice(0, 32);
190
+ return `state-reinject-${key}.json`;
191
+ }
192
+
193
+ // This hook's files in the shared directory: markers, and one a crash left mid-take.
194
+ const MARKER_FILE_RE = /^state-reinject-[0-9a-f]{32}\.json(?:\.\d+\.taken)?$/;
195
+
196
+ function writeMarker(file, now = Date.now()) {
197
+ fs.writeFileSync(file, JSON.stringify({ at: now }), { mode: 0o600 });
198
+ // Sweep markers no session came back for — only this hook's: the directory
199
+ // holds the other hooks' state too. Best effort.
200
+ try {
201
+ for (const name of fs.readdirSync(path.dirname(file))) {
202
+ if (!MARKER_FILE_RE.test(name)) continue;
203
+ const p = path.join(path.dirname(file), name);
204
+ try { if (now - fs.statSync(p).mtimeMs > MARKER_TTL_MS) fs.unlinkSync(p); } catch { /* raced */ }
205
+ }
206
+ } catch { /* unreadable dir */ }
207
+ }
208
+
209
+ /**
210
+ * Take the marker if there is a fresh one: rename it first, so two hooks racing on
211
+ * the same tool result cannot both inject. True when this call took it.
212
+ */
213
+ function takeMarker(file, now = Date.now()) {
214
+ const taken = `${file}.${process.pid}.taken`;
215
+ try { fs.renameSync(file, taken); } catch { return false; }
216
+ let fresh = false;
217
+ try { fresh = now - JSON.parse(fs.readFileSync(taken, 'utf8')).at <= MARKER_TTL_MS; } catch { /* unreadable: stale */ }
218
+ try { fs.unlinkSync(taken); } catch { /* gone */ }
219
+ return fresh;
220
+ }
221
+
222
+ /** The block in the shape each host's tool-result event takes. */
223
+ function injectOutput(host, context) {
224
+ if (host === 'gemini') return { hookSpecificOutput: { hookEventName: 'AfterTool', additionalContext: context } };
225
+ if (host === 'copilot') return { additionalContext: context };
226
+ return null;
227
+ }
228
+
229
+ function contextFor(projectDir) {
230
+ return buildReinjectContext({
231
+ stateContent: readIfExists(planningPath(projectDir, 'state.md')),
232
+ roadmapContent: readIfExists(planningPath(projectDir, 'roadmap.md')),
233
+ planningRel: planningDirName(),
234
+ });
235
+ }
236
+
127
237
  function main() {
238
+ const argv = process.argv.slice(2);
239
+ const mode = argv.includes('--mark') ? 'mark' : argv.includes('--inject') ? 'inject' : 'session-start';
240
+ const host = mode === 'inject' ? argv[argv.indexOf('--inject') + 1] : null;
128
241
  let input = '';
129
242
  process.stdin.setEncoding('utf8');
130
243
  process.stdin.on('data', chunk => { input += chunk; });
@@ -133,20 +246,32 @@ function main() {
133
246
  let payload = {};
134
247
  try { payload = JSON.parse(input); } catch { /* fail open on bad stdin */ }
135
248
  if (!payload || typeof payload !== 'object') payload = {};
136
- if (payload.source !== 'compact') { process.exit(0); return; }
137
-
138
249
  const projectDir = typeof payload.cwd === 'string' && payload.cwd ? payload.cwd : process.cwd();
139
- const context = buildReinjectContext({
140
- stateContent: readIfExists(planningPath(projectDir, 'state.md')),
141
- roadmapContent: readIfExists(planningPath(projectDir, 'roadmap.md')),
142
- planningRel: planningDirName(),
143
- });
144
- if (context) {
145
- process.stdout.write(JSON.stringify({
146
- hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: context },
147
- }));
250
+
251
+ if (mode === 'session-start') {
252
+ if (payload.source !== 'compact') { process.exit(0); return; }
253
+ const context = contextFor(projectDir);
254
+ if (context) {
255
+ process.stdout.write(JSON.stringify({
256
+ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: context },
257
+ }));
258
+ }
259
+ } else {
260
+ const sid = sessionIdOf(payload);
261
+ // An unknown host leaves the marker for a host that can use it.
262
+ if (!sid || (mode === 'inject' && !injectOutput(host, ''))) { process.exit(0); return; }
263
+ const dir = hookStateDir();
264
+ if (!dir) { process.exit(0); return; }
265
+ const file = path.join(dir, markerName(sid, projectDir));
266
+ if (mode === 'mark') {
267
+ if (contextFor(projectDir)) writeMarker(file);
268
+ } else if (takeMarker(file)) {
269
+ // Re-read now: the position after the compaction, not before it.
270
+ const context = contextFor(projectDir);
271
+ if (context) process.stdout.write(JSON.stringify(injectOutput(host, context)));
272
+ }
148
273
  }
149
- } catch { /* fail open — never break a session start */ }
274
+ } catch { /* fail open — never break a session or a tool call */ }
150
275
  process.exit(0);
151
276
  });
152
277
  }
@@ -155,4 +280,4 @@ if (require.main === module) {
155
280
  main();
156
281
  }
157
282
 
158
- module.exports = { buildReinjectContext, UNTICKED_PHASE_RE };
283
+ module.exports = { buildReinjectContext, UNTICKED_PHASE_RE, markerName, hookDirName, sessionIdOf, injectOutput, MARKER_TTL_MS };
@@ -30,6 +30,54 @@ function buildStatuslineOutput(data, deps) {
30
30
  const session = data.session_id || '';
31
31
  const remaining = data.context_window?.remaining_percentage;
32
32
 
33
+ // The per-user 0700 bridge directory, or null when it is not safe to write into.
34
+ // Mirrors bridgeDir() in pan-context-monitor.js (the reader).
35
+ let bridgeSubdirMemo;
36
+ const secureBridgeDir = () => {
37
+ if (bridgeSubdirMemo !== undefined) return bridgeSubdirMemo;
38
+ bridgeSubdirMemo = null;
39
+ const uid = (typeof process.getuid === 'function' ? process.getuid() : process.env.USERNAME || 'win');
40
+ const sub = pathMod.join(tmpDir, `pan-hooks-${uid}`);
41
+ // Fail CLOSED if the per-user dir is pre-planted/symlinked/owned by someone
42
+ // else: mkdirSync{recursive} silently no-ops on an existing dir, so verify
43
+ // ownership+mode before writing (M60).
44
+ try {
45
+ fsMod.mkdirSync(sub, { recursive: true, mode: 0o700 });
46
+ const st = fsMod.lstatSync(sub);
47
+ // Symlink check is cross-platform; POSIX ownership/mode checks apply only
48
+ // where getuid exists (Windows fakes mode bits; gating on them there
49
+ // disabled the bridge entirely, N15).
50
+ let secure = !st.isSymbolicLink();
51
+ if (secure && typeof process.getuid === 'function') {
52
+ secure = st.uid === process.getuid() && (st.mode & 0o077) === 0;
53
+ }
54
+ if (secure) bridgeSubdirMemo = sub;
55
+ } catch { /* not safe */ }
56
+ return bridgeSubdirMemo;
57
+ };
58
+
59
+ // Usage limits (Claude Code subscriptions: `rate_limits.five_hour` / `seven_day`).
60
+ // Account-wide, so one file for every session; `pan-tools cost limits` and the
61
+ // focus-auto weekly stop read it. Written only when the host sent the block.
62
+ const limits = data.rate_limits && typeof data.rate_limits === 'object' ? data.rate_limits : null;
63
+ const limitWin = (w) => (w && Number.isFinite(Number(w.used_percentage))
64
+ ? { used_percentage: Number(w.used_percentage), ...(Number.isFinite(Number(w.resets_at)) ? { resets_at: Number(w.resets_at) } : {}) }
65
+ : null);
66
+ const fiveHour = limits ? limitWin(limits.five_hour) : null;
67
+ const sevenDay = limits ? limitWin(limits.seven_day) : null;
68
+ if ((fiveHour || sevenDay) && d.skipBridge !== true) {
69
+ try {
70
+ const sub = secureBridgeDir();
71
+ if (sub) {
72
+ fsMod.writeFileSync(pathMod.join(sub, 'claude-limits.json'), JSON.stringify({
73
+ ...(fiveHour ? { five_hour: fiveHour } : {}),
74
+ ...(sevenDay ? { seven_day: sevenDay } : {}),
75
+ timestamp: Math.floor(Date.now() / 1000),
76
+ }), { mode: 0o600 });
77
+ }
78
+ } catch { /* best-effort */ }
79
+ }
80
+
33
81
  // Context window bar — shows USED percentage scaled so 80% real = 100% shown.
34
82
  let ctx = '';
35
83
  if (remaining != null) {
@@ -39,34 +87,23 @@ function buildStatuslineOutput(data, deps) {
39
87
 
40
88
  if (session && d.skipBridge !== true) {
41
89
  try {
42
- // Write the bridge file into a per-user 0700 subdir so another user on
90
+ // The session bridge goes into the per-user 0700 subdir so another user on
43
91
  // a shared host can't symlink-attack the predictable session path.
44
- // Mirrors bridgeDir() in pan-context-monitor.js (the reader).
45
- const uid = (typeof process.getuid === 'function' ? process.getuid() : process.env.USERNAME || 'win');
46
- const bridgeSubdir = pathMod.join(tmpDir, `pan-hooks-${uid}`);
47
- // Fail CLOSED if the per-user dir is pre-planted/symlinked/owned by
48
- // someone else — mkdirSync{recursive} silently no-ops on an existing dir,
49
- // so verify ownership+mode before writing the session bridge (M60).
50
- let secure = false;
51
- try {
52
- fsMod.mkdirSync(bridgeSubdir, { recursive: true, mode: 0o700 });
53
- const st = fsMod.lstatSync(bridgeSubdir);
54
- // Symlink check is cross-platform; POSIX ownership/mode checks apply
55
- // only where getuid exists (Windows fakes mode bits — gating on them
56
- // there disabled the bridge entirely, N15).
57
- secure = !st.isSymbolicLink();
58
- if (secure && typeof process.getuid === 'function') {
59
- secure = st.uid === process.getuid() && (st.mode & 0o077) === 0;
60
- }
61
- } catch { secure = false; }
62
- if (secure) {
92
+ const bridgeSubdir = secureBridgeDir();
93
+ if (bridgeSubdir) {
63
94
  const bridgePath = pathMod.join(bridgeSubdir, `claude-ctx-${session}.json`);
95
+ // Token counts and the model id let the context monitor measure against
96
+ // the window the host compacts at, not the model window (O7).
97
+ const cw = data.context_window || {};
64
98
  fsMod.writeFileSync(bridgePath, JSON.stringify({
65
99
  session_id: session,
66
100
  remaining_percentage: remaining,
67
101
  used_pct: used,
102
+ ...(Number.isFinite(cw.total_input_tokens) ? { total_input_tokens: cw.total_input_tokens } : {}),
103
+ ...(Number.isFinite(cw.context_window_size) ? { context_window_size: cw.context_window_size } : {}),
104
+ ...(data.model && data.model.id ? { model_id: data.model.id } : {}),
68
105
  timestamp: Math.floor(Date.now() / 1000),
69
- }));
106
+ }), { mode: 0o600 });
70
107
  }
71
108
  } catch { /* bridge is best-effort */ }
72
109
  }
@@ -154,7 +191,16 @@ function buildStatuslineOutput(data, deps) {
154
191
  const head = `${panUpdate}\x1b[2m${model}\x1b[0m`;
155
192
  const taskSegment = task ? ` │ \x1b[1m${task}\x1b[0m` : '';
156
193
  const dirSegment = ` │ \x1b[2m${dirname}\x1b[0m`;
157
- return `${head}${taskSegment}${dirSegment}${ctx}${cacheBadge}${thinkingBadge}`;
194
+ // Usage-limit badge: the window closer to its cap, green below 70%, yellow below
195
+ // 90%, red from 90% (focus-auto's default weekly stop).
196
+ let limitBadge = '';
197
+ const shown = [fiveHour && ['5h', fiveHour.used_percentage], sevenDay && ['7d', sevenDay.used_percentage]].filter(Boolean);
198
+ if (shown.length) {
199
+ const worst = Math.max(...shown.map(([, v]) => v));
200
+ const color = worst >= 90 ? '' : worst >= 70 ? '' : '';
201
+ limitBadge = ` ${color}${shown.map(([k, v]) => `${k} ${Math.round(v)}%`).join(' · ')}`;
202
+ }
203
+ return `${head}${taskSegment}${dirSegment}${ctx}${cacheBadge}${thinkingBadge}${limitBadge}`;
158
204
  }
159
205
 
160
206
  // ─── Stdin driver ───────────────────────────────────────────────────────────
@@ -73,6 +73,10 @@ const fs = require('fs');
73
73
  const os = require('os');
74
74
  const path = require('path');
75
75
 
76
+ // The CLI the block reason tells the agent to run. Installs and the plugin put the
77
+ // core beside hooks/; no `pan-tools` bin is on PATH.
78
+ const PAN_TOOLS = `node "${path.join(__dirname, '..', 'pan-wizard-core', 'bin', 'pan-tools.cjs').split(path.sep).join('/')}"`;
79
+
76
80
  // ─── R39: one run per hook when Claude and Copilot share a project ───────────
77
81
  // Copilot CLI also runs the hooks in a repository's .claude/settings.json and
78
82
  // .claude/settings.local.json. Measured 2026-09-26 (Copilot CLI 1.0.88, repository
@@ -205,8 +209,9 @@ function buildStopDecision({ stopHookActive, config, stateContent, roadmapConten
205
209
  `Continue the chain now: if the current phase is finished, follow transition.md's ` +
206
210
  `offer_next_phase Route A and spawn Phase ${nextPhase} as a Task subagent ` +
207
211
  `(plan-phase orchestrator, ARGUMENTS='${nextPhase} --auto'); if the current phase is ` +
208
- `mid-flight, resume it instead. If the user explicitly asked to stop, first run: ` +
209
- `pan-tools config-set workflow.auto_advance false — then stop. ` +
212
+ `mid-flight, resume it instead. If the user explicitly asked to stop, first run ` +
213
+ `\`${PAN_TOOLS} config-set workflow.auto_advance false\` (under mode: yolo, which arms this ` +
214
+ `guard by itself, set \`workflow.stop_guard false\` the same way instead) — then stop. ` +
210
215
  `(Disable this guard permanently with workflow.stop_guard: false.)`
211
216
  };
212
217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pan-wizard",
3
- "version": "3.32.0",
3
+ "version": "3.34.0",
4
4
  "description": "Command a bot army for your codebase: a reasoning-tier Mission Control delegates whole-project goals to specialist squads and ships behind a human merge gate. Five AI CLIs, zero context rot.",
5
5
  "bin": {
6
6
  "pan-wizard": "bin/install.js"
@@ -29,7 +29,10 @@ function buildAgentsMdSection() {
29
29
  'This project uses PAN Wizard for structured, phase-based planning and execution.',
30
30
  '',
31
31
  '- `.planning/` is PAN\'s state directory (state.md, roadmap.md, phase directories). Treat it as the source of truth for planning state and modify it through PAN commands, not by hand.',
32
- '- PAN commands install as `pan-*` skills/commands (for example `/pan-help`, `/pan-new-project`, `/pan-exec-phase`). Start with `/pan-help`.',
32
+ // One section serves every runtime in the project, so it names each one's form
33
+ // (the README's list); `/pan-help` alone sent Claude Code users to a command
34
+ // that does not exist there (prompt audit, market-ideas M34).
35
+ '- PAN\'s commands are named per runtime: `/pan:help`, `/pan:new-project`, `/pan:exec-phase` in Claude Code and Gemini CLI; `/pan-help` and so on in OpenCode, Copilot CLI and unified-skills Claude installs; `$pan-help` in Codex. Start with the help command.',
33
36
  '- The `pan-tools` dispatcher backs every command; it lives under `pan-wizard-core/` inside the runtime\'s config directory (or `.agents/` for unified installs).',
34
37
  PAN_AGENTS_END,
35
38
  ].join('\n');
@@ -79,19 +82,56 @@ function removeAgentsMdSection(existing) {
79
82
  }
80
83
 
81
84
  /**
82
- * Ensure CLAUDE.md bridges to AGENTS.md via a marker-fenced @AGENTS.md import
83
- * (Claude Code's documented pattern for adopting the universal rules file).
84
- * Idempotent; preserves all user content.
85
+ * PAN's block in CLAUDE.md: the @AGENTS.md import, and Claude Code's "Compact
86
+ * instructions" section (memory optimisation O8). Claude Code reads a section of
87
+ * that name in the project-root CLAUDE.md when it summarises a conversation, and
88
+ * re-reads CLAUDE.md from disk after compaction. The section names what the summary
89
+ * must keep for PAN work to resume where it stopped.
90
+ *
91
+ * Codex is deliberately left out: its `compact_prompt` replaces the built-in
92
+ * compaction prompt rather than adding to it, and the default OpenAI provider
93
+ * ignores it (read 2026-10-04), so PAN would be swapping a tuned prompt for one
94
+ * that may never run. PAN's state re-injection after compaction covers Codex.
95
+ */
96
+ function buildClaudeMdBridge() {
97
+ return [
98
+ PAN_AGENTS_BEGIN,
99
+ '@AGENTS.md',
100
+ '',
101
+ '# Compact instructions',
102
+ '',
103
+ 'When this conversation is compacted, keep what PAN work needs to resume where it stopped:',
104
+ '- the current phase and plan, and the task in progress with its stopping point (`.planning/state.md` records them; keep that path);',
105
+ '- decisions made in this session that `.planning/state.md` does not yet hold;',
106
+ '- the files changed since the last commit.',
107
+ 'Leave out file contents and command output: they can be read again.',
108
+ PAN_AGENTS_END,
109
+ ].join('\n');
110
+ }
111
+
112
+ /**
113
+ * Ensure CLAUDE.md carries PAN's block: the marker-fenced @AGENTS.md import
114
+ * (Claude Code's documented pattern for adopting the universal rules file) and
115
+ * the compact instructions. Idempotent; preserves all user content. An older PAN
116
+ * bridge (the import alone) is replaced in place, so a reinstall or `memory
117
+ * rebuild` brings existing projects up to date.
85
118
  * @param {string|null} existing - Current CLAUDE.md content, or null if absent
86
119
  * @returns {string} New file content
87
120
  */
88
121
  function ensureClaudeMdImport(existing) {
89
- const block = `${PAN_AGENTS_BEGIN}\n@AGENTS.md\n${PAN_AGENTS_END}`;
122
+ const block = buildClaudeMdBridge();
90
123
  if (!existing || !existing.trim()) {
91
124
  return block + '\n';
92
125
  }
93
- if (existing.includes(PAN_AGENTS_BEGIN)) {
94
- return existing; // bridge (or another PAN block) already present
126
+ const beginIdx = existing.indexOf(PAN_AGENTS_BEGIN);
127
+ const endIdx = existing.indexOf(PAN_AGENTS_END);
128
+ if (beginIdx !== -1) {
129
+ // PAN's own bridge: bring it up to date. Any other PAN block is left alone.
130
+ if (endIdx > beginIdx && /^@AGENTS\.md\s*$/m.test(existing.slice(beginIdx, endIdx))) {
131
+ const eol = existing.includes('\r\n') ? '\r\n' : '\n';
132
+ return existing.slice(0, beginIdx) + block.replace(/\n/g, eol) + existing.slice(endIdx + PAN_AGENTS_END.length);
133
+ }
134
+ return existing;
95
135
  }
96
136
  if (/^@AGENTS\.md\s*$/m.test(existing)) {
97
137
  return existing; // user already imports AGENTS.md themselves
@@ -115,6 +155,7 @@ module.exports = {
115
155
  buildAgentsMdSection,
116
156
  upsertAgentsMdSection,
117
157
  removeAgentsMdSection,
158
+ buildClaudeMdBridge,
118
159
  ensureClaudeMdImport,
119
160
  removeClaudeMdImport,
120
161
  };
@@ -23,8 +23,9 @@
23
23
  * ]
24
24
  * }
25
25
  *
26
- * Populating this file is the host runtime's responsibility (Claude Code's
27
- * MCP list API, etc.). This module reads the cache and reasons over it.
26
+ * Nothing populates this file on its own: no hook or host writes it.
27
+ * `bridge cache --servers <json>` (or an external script) seeds it; this module
28
+ * reads the cache and reasons over it.
28
29
  */
29
30
 
30
31
  const fs = require('fs');
@@ -184,7 +185,7 @@ function recommendForPhase(cwd, phaseNum, opts) {
184
185
  phase: String(phaseNum),
185
186
  runtime: cache.runtime,
186
187
  recommendations: [],
187
- reason: 'no MCP tools cached — run `pan-tools bridge cache` or ensure host runtime populates .planning/bridge/available-tools.json',
188
+ reason: 'no MCP tools cached — seed .planning/bridge/available-tools.json with `pan-tools bridge cache --servers <json>`; no hook or host writes it',
188
189
  };
189
190
  }
190
191
 
@@ -240,8 +241,8 @@ function cmdBridgeRecommend(cwd, phaseNum, opts, raw) {
240
241
  }
241
242
 
242
243
  function cmdBridgeCache(cwd, serversJson, runtime, raw) {
243
- // For scripted cache writes. Normally the host runtime writes the file,
244
- // but this CLI path lets users seed it for testing or from external scripts.
244
+ // The cache's only writer: no hook or host runtime populates the file, so this
245
+ // CLI path (or an external script) is how users seed it.
245
246
  if (!serversJson) {
246
247
  // No payload — just echo the current cache path/state.
247
248
  output(listTools(cwd), raw);
@@ -66,12 +66,14 @@ function writeScheduleFile(cwd, schedule) {
66
66
  * @returns {object|{error}} the written descriptor
67
67
  */
68
68
  function writeSchedule(cwd, opts, now) {
69
- const cadence = opts.cadence || 'daily';
69
+ // An update keeps the stored cadence like every other field: `--pause` on an
70
+ // hourly campaign used to set it back to daily.
71
+ const existing = readSchedule(cwd) || {};
72
+ const cadence = opts.cadence || existing.cadence || 'daily';
70
73
  if (parseCadence(cadence) === null) {
71
74
  return { error: `Invalid cadence "${cadence}". Use hourly | daily | weekly | Nh | Nd.` };
72
75
  }
73
76
  const at = now || new Date();
74
- const existing = readSchedule(cwd) || {};
75
77
  const schedule = {
76
78
  goal: opts.goal ?? existing.goal ?? null,
77
79
  source: opts.source ?? existing.source ?? 'backlog',
@@ -299,6 +299,23 @@ function formatLearningEntry(learning) {
299
299
  return lines.join('\n');
300
300
  }
301
301
 
302
+ /**
303
+ * A summary's `key-files` as one list. The summary templates write `{created, modified}`;
304
+ * older summaries carry a flat list. Only the flat list was read, so summaries PAN writes
305
+ * gave no co-change learnings, and one beside a flat-list summary crashed on `.includes`.
306
+ * @param {Object} fm - Summary frontmatter
307
+ * @returns {string[]}
308
+ */
309
+ function keyFileList(fm) {
310
+ const kf = fm['key-files'] || fm.key_files;
311
+ if (Array.isArray(kf)) return kf.map(String);
312
+ if (kf && typeof kf === 'object') {
313
+ const list = (v) => (Array.isArray(v) ? v : (v ? [v] : []));
314
+ return [...new Set([...list(kf.created), ...list(kf.modified)].map(String))];
315
+ }
316
+ return [];
317
+ }
318
+
302
319
  /**
303
320
  * Extract learnings from session summaries and error patterns.
304
321
  * Reads session history + error patterns, extracts file co-change patterns
@@ -347,7 +364,7 @@ function cmdLearningsExtract(cwd, raw) {
347
364
  const fileCoChanges = new Map(); // file -> Set of co-changed files
348
365
 
349
366
  for (const { frontmatter } of summaries) {
350
- const keyFiles = Array.isArray(frontmatter['key-files']) ? frontmatter['key-files'] : [];
367
+ const keyFiles = keyFileList(frontmatter);
351
368
  if (keyFiles.length < 2) continue;
352
369
  for (const file of keyFiles) {
353
370
  if (!fileCoChanges.has(file)) fileCoChanges.set(file, new Set());
@@ -368,7 +385,7 @@ function cmdLearningsExtract(cwd, raw) {
368
385
  // Count co-occurrences
369
386
  let count = 0;
370
387
  for (const { frontmatter } of summaries) {
371
- const kf = frontmatter['key-files'] || [];
388
+ const kf = keyFileList(frontmatter);
372
389
  if (kf.includes(file) && kf.includes(coFile)) count++;
373
390
  }
374
391
  if (count < 2) continue;