pan-wizard 3.32.0 → 3.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/pan-conductor.md +2 -2
- package/agents/pan-document_code.md +2 -2
- package/agents/pan-executor.md +3 -3
- package/agents/pan-experiment-runner.md +7 -10
- package/agents/pan-hardener.md +15 -15
- package/agents/pan-knowledge.md +1 -1
- package/agents/pan-meta-reviewer.md +2 -2
- package/agents/pan-optimizer.md +2 -2
- package/agents/pan-phase-researcher.md +2 -2
- package/agents/pan-plan-checker.md +6 -2
- package/agents/pan-planner.md +23 -10
- package/agents/pan-project-researcher.md +10 -67
- package/agents/pan-roadmapper.md +5 -48
- package/agents/pan-verifier.md +26 -11
- package/bin/install-lib.cjs +170 -15
- package/bin/install.js +39 -14
- package/commands/pan/army.md +2 -2
- package/commands/pan/experiment.md +2 -2
- package/commands/pan/focus-auto.md +12 -8
- package/commands/pan/focus-design.md +2 -0
- package/commands/pan/focus-exec.md +1 -1
- package/commands/pan/focus-scan.md +2 -7
- package/commands/pan/map-codebase.md +4 -4
- package/commands/pan/mcp-bridge.md +1 -1
- package/commands/pan/milestone-done.md +1 -2
- package/commands/pan/phase-tests.md +2 -2
- package/commands/pan/profile.md +2 -2
- package/commands/pan/research-phase.md +2 -4
- package/commands/pan/review-deep.md +3 -3
- package/commands/pan/todo-add.md +2 -2
- package/commands/pan/todo-check.md +2 -2
- package/hooks/dist/pan-state-reinject.js +140 -15
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +4 -1
- package/pan-wizard-core/bin/lib/commands.cjs +93 -1
- package/pan-wizard-core/bin/lib/config.cjs +110 -0
- package/pan-wizard-core/bin/lib/constants.cjs +11 -9
- package/pan-wizard-core/bin/lib/core.cjs +27 -7
- package/pan-wizard-core/bin/lib/cost.cjs +13 -3
- package/pan-wizard-core/bin/lib/findings.cjs +6 -2
- package/pan-wizard-core/bin/lib/init.cjs +5 -1
- package/pan-wizard-core/bin/lib/phase.cjs +52 -2
- package/pan-wizard-core/bin/lib/review-deep.cjs +2 -2
- package/pan-wizard-core/bin/lib/state.cjs +3 -2
- package/pan-wizard-core/bin/lib/verdict.cjs +54 -4
- package/pan-wizard-core/bin/lib/verify-drift.cjs +14 -5
- package/pan-wizard-core/bin/lib/verify-scope.cjs +69 -2
- package/pan-wizard-core/bin/lib/verify.cjs +87 -7
- package/pan-wizard-core/bin/pan-tools.cjs +10 -2
- package/pan-wizard-core/mcp/server.cjs +18 -9
- package/pan-wizard-core/references/checkpoints.md +4 -2
- package/pan-wizard-core/references/verdict-contract.md +2 -1
- package/pan-wizard-core/templates/discovery.md +1 -1
- package/pan-wizard-core/templates/phase-prompt.md +2 -1
- package/pan-wizard-core/templates/verification-report.md +5 -0
- package/pan-wizard-core/workflows/exec-phase.md +8 -3
- package/pan-wizard-core/workflows/health.md +1 -0
- package/pan-wizard-core/workflows/new-project.md +1 -1
- package/pan-wizard-core/workflows/quick.md +6 -6
- package/pan-wizard-core/workflows/verify-phase.md +7 -2
- package/scripts/build-agent-plugin.js +19 -5
- package/scripts/test-surface.cjs +4 -0
|
@@ -73,10 +73,7 @@ Read these files to establish baseline:
|
|
|
73
73
|
|
|
74
74
|
**Project Structure:**
|
|
75
75
|
- All core modules in the project's source directories
|
|
76
|
-
-
|
|
77
|
-
- All agent definitions
|
|
78
|
-
- All workflow definitions
|
|
79
|
-
- All hook source files
|
|
76
|
+
- The project's own command, agent, workflow and hook definitions, if it has any (never PAN's, which the Project Scope Boundary excludes)
|
|
80
77
|
|
|
81
78
|
**Planning State:**
|
|
82
79
|
- `.planning/state.md` — Current state
|
|
@@ -95,9 +92,7 @@ In: source directories, hooks, tests
|
|
|
95
92
|
Cross-reference found issues with documented items — flag any UNDOCUMENTED issues as new findings.
|
|
96
93
|
|
|
97
94
|
### 0.3 Check Test Results
|
|
98
|
-
|
|
99
|
-
npm test 2>&1
|
|
100
|
-
```
|
|
95
|
+
Run the project's test suite: the commands in `.planning/config.json → verification` when set, otherwise the test command discovered from `package.json` scripts, `Makefile`, or project docs.
|
|
101
96
|
Record: total tests, passing, failing, suites.
|
|
102
97
|
|
|
103
98
|
### 0.4 Baseline Snapshot Table
|
|
@@ -17,7 +17,7 @@ Analyze existing codebase using parallel pan-document_code agents to produce str
|
|
|
17
17
|
|
|
18
18
|
Each mapper agent explores a focus area and **writes documents directly** to `.planning/codebase/`. The orchestrator only receives confirmations, keeping context usage minimal.
|
|
19
19
|
|
|
20
|
-
Output: .planning/codebase/ folder with
|
|
20
|
+
Output: .planning/codebase/ folder with structured documents about the codebase state.
|
|
21
21
|
</objective>
|
|
22
22
|
|
|
23
23
|
<execution_context>
|
|
@@ -61,7 +61,7 @@ The CLI returns `{mode, total_tokens, file_count, languages}`:
|
|
|
61
61
|
|
|
62
62
|
Record the chosen mode + telemetry in the final `.planning/codebase/overview.md` so future runs can reason about drift.
|
|
63
63
|
|
|
64
|
-
**The mode is decided by repo size alone** — `estimate-size` compares the token estimate to `--threshold` and applies no model check. So single-shot only pays off when the model you launched with actually has a 1M-context window
|
|
64
|
+
**The mode is decided by repo size alone** — `estimate-size` compares the token estimate to `--threshold` and applies no model check. So single-shot only pays off when the model you launched with actually has a 1M-context window. On a model with a smaller window, pass a threshold that matches your real window (e.g. `--threshold 150000`) so anything larger resolves to `sharded` instead of overflowing a single agent.
|
|
65
65
|
</stage_0_ingest_mode>
|
|
66
66
|
|
|
67
67
|
<tool_priority>
|
|
@@ -99,14 +99,14 @@ The orchestrator loads context in layers — NOT everything upfront. Mapper agen
|
|
|
99
99
|
- Agent 5: relationships focus → writes relationships.md
|
|
100
100
|
- Agent 6: practices focus → writes best-practices.md
|
|
101
101
|
4. Wait for agents to complete, collect confirmations (NOT document contents)
|
|
102
|
-
5. Verify all
|
|
102
|
+
5. Verify all documents exist with line counts
|
|
103
103
|
6. Commit codebase map
|
|
104
104
|
7. Offer next steps (typically: /pan:new-project or /pan:plan-phase)
|
|
105
105
|
</process>
|
|
106
106
|
|
|
107
107
|
<success_criteria>
|
|
108
108
|
- [ ] .planning/codebase/ directory created
|
|
109
|
-
- [ ] All
|
|
109
|
+
- [ ] All codebase documents written by mapper agents
|
|
110
110
|
- [ ] Documents follow template structure
|
|
111
111
|
- [ ] Parallel agents completed without errors
|
|
112
112
|
- [ ] User knows next steps
|
|
@@ -98,7 +98,7 @@ Normally the host runtime writes this file. The CLI path exists for test fixture
|
|
|
98
98
|
|
|
99
99
|
<workflow>
|
|
100
100
|
|
|
101
|
-
**New to a project with MCP tools?** Run `/pan:mcp-bridge list` to see what's available. If empty, check the host runtime's MCP config — each runtime keeps it somewhere different: Claude Code in `
|
|
101
|
+
**New to a project with MCP tools?** Run `/pan:mcp-bridge list` to see what's available. If empty, check the host runtime's MCP config — each runtime keeps it somewhere different: Claude Code in `.mcp.json` at the project root (project scope) or `~/.claude.json` (user and local scopes), OpenCode in `opencode.json`, Copilot in `.github/mcp.json`, Codex in its own config, Gemini in its settings file.
|
|
102
102
|
|
|
103
103
|
**Planning a phase that might touch external systems?** Run `/pan:mcp-bridge recommend <phase>` to get a ranked shortlist. Copy relevant tool names into the phase plan's "External tools" section so the executor knows to invoke them.
|
|
104
104
|
|
|
@@ -34,7 +34,7 @@ The full milestone-done workflow is inlined in <process> below — there is no s
|
|
|
34
34
|
|
|
35
35
|
**User input:**
|
|
36
36
|
|
|
37
|
-
- Version:
|
|
37
|
+
- Version: $ARGUMENTS (e.g., "1.0", "1.1", "2.0"); `{{version}}` below stands for this value
|
|
38
38
|
</context>
|
|
39
39
|
|
|
40
40
|
<process>
|
|
@@ -137,7 +137,6 @@ The full milestone-done workflow is inlined in <process> below — there is no s
|
|
|
137
137
|
|
|
138
138
|
<critical_rules>
|
|
139
139
|
|
|
140
|
-
- **Load workflow first:** Read milestone-done.md before executing
|
|
141
140
|
- **Verify completion:** All phases must have summary.md files
|
|
142
141
|
- **User confirmation:** Wait for approval at verification gates
|
|
143
142
|
- **Archive before deleting:** Always create archive files before updating/deleting originals
|
|
@@ -19,7 +19,7 @@ Generate unit and E2E tests for a completed phase, using its summary.md, context
|
|
|
19
19
|
|
|
20
20
|
Analyzes implementation files, classifies them into TDD (unit), E2E (browser), or Skip categories, presents a test plan for user approval, then generates tests following RED-GREEN conventions.
|
|
21
21
|
|
|
22
|
-
Output: Test files committed with message `test(phase-{N}): add unit and E2E tests from
|
|
22
|
+
Output: Test files committed with message `test(phase-{N}): add unit and E2E tests from phase-tests command`
|
|
23
23
|
</objective>
|
|
24
24
|
|
|
25
25
|
<execution_context>
|
|
@@ -34,6 +34,6 @@ Phase: $ARGUMENTS
|
|
|
34
34
|
</context>
|
|
35
35
|
|
|
36
36
|
<process>
|
|
37
|
-
Execute the
|
|
37
|
+
Execute the phase-tests workflow from @~/.claude/pan-wizard-core/workflows/phase-tests.md end-to-end.
|
|
38
38
|
Preserve all workflow gates (classification approval, test plan approval, RED-GREEN verification, gap reporting).
|
|
39
39
|
</process>
|
package/commands/pan/profile.md
CHANGED
|
@@ -12,7 +12,7 @@ allowed-tools:
|
|
|
12
12
|
<objective>
|
|
13
13
|
Switch the model profile used by PAN agents. Controls which Claude model each agent uses, balancing quality vs token spend.
|
|
14
14
|
|
|
15
|
-
Routes to the
|
|
15
|
+
Routes to the profile workflow which handles:
|
|
16
16
|
- Argument validation (quality/balanced/budget)
|
|
17
17
|
- Downgrade confirmation (quality → balanced → budget requires 'yes')
|
|
18
18
|
- Config file creation if missing
|
|
@@ -25,7 +25,7 @@ Routes to the set-profile workflow which handles:
|
|
|
25
25
|
</execution_context>
|
|
26
26
|
|
|
27
27
|
<process>
|
|
28
|
-
**Follow the
|
|
28
|
+
**Follow the profile workflow** from `@~/.claude/pan-wizard-core/workflows/profile.md`.
|
|
29
29
|
|
|
30
30
|
The workflow handles all logic including:
|
|
31
31
|
1. Profile argument validation
|
|
@@ -55,11 +55,9 @@ PHASE_INFO=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs roadmap get-phase
|
|
|
55
55
|
|
|
56
56
|
## 2. Check Existing Research
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
ls .planning/phases/${PHASE}-*/research.md 2>/dev/null
|
|
60
|
-
```
|
|
58
|
+
Use `has_research` and `research_path` from INIT.
|
|
61
59
|
|
|
62
|
-
**If exists:** Offer: 1) Update research, 2) View existing, 3) Skip. Wait for response.
|
|
60
|
+
**If exists:** Offer: 1) Update research, 2) View existing (`research_path`), 3) Skip. Wait for response.
|
|
63
61
|
|
|
64
62
|
**If doesn't exist:** Continue.
|
|
65
63
|
|
|
@@ -111,18 +111,18 @@ Verdict is driven by the highest-severity finding across all three sources. Meta
|
|
|
111
111
|
|
|
112
112
|
| Runtime | hardener | meta-reviewer | merge |
|
|
113
113
|
|---------|----------|---------------|-------|
|
|
114
|
-
| Claude Code | Full
|
|
114
|
+
| Claude Code | Full; reasoning depth from the agent's `effort:` frontmatter | Full | Full |
|
|
115
115
|
| OpenCode | Prose "think step-by-step" preamble substitutes for thinking | Same | Full (runtime-agnostic CLI) |
|
|
116
116
|
| Gemini | Same | Same | Full |
|
|
117
117
|
| Codex | Same | Same | Full |
|
|
118
118
|
| Copilot | Same | Same | Full |
|
|
119
119
|
|
|
120
|
-
The merger CLI (`pan-tools review-deep merge`) is pure Node.js and works identically across runtimes. Only the *quality* of the hardener and meta-reviewer outputs varies with model capability
|
|
120
|
+
The merger CLI (`pan-tools review-deep merge`) is pure Node.js and works identically across runtimes. Only the *quality* of the hardener and meta-reviewer outputs varies with model capability.
|
|
121
121
|
|
|
122
122
|
</runtime_compatibility>
|
|
123
123
|
|
|
124
124
|
<calibration_note>
|
|
125
125
|
|
|
126
|
-
Deep review is opt-in for a reason: it costs roughly 3× a normal review (hardener + meta + merge adds two agent spawns per phase). Use it for high-stakes phases, not every phase.
|
|
126
|
+
Deep review is opt-in for a reason: it costs roughly 3× a normal review (hardener + meta + merge adds two agent spawns per phase). Use it for high-stakes phases, not every phase.
|
|
127
127
|
|
|
128
128
|
</calibration_note>
|
package/commands/pan/todo-add.md
CHANGED
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
<objective>
|
|
14
14
|
Capture an idea, task, or issue that surfaces during a PAN session as a structured todo for later work.
|
|
15
15
|
|
|
16
|
-
Routes to the add
|
|
16
|
+
Routes to the todo-add workflow which handles:
|
|
17
17
|
- Directory structure creation
|
|
18
18
|
- Content extraction from arguments or conversation
|
|
19
19
|
- Area inference from file paths
|
|
@@ -34,7 +34,7 @@ State is resolved in-workflow via `init todos` and targeted reads.
|
|
|
34
34
|
</context>
|
|
35
35
|
|
|
36
36
|
<process>
|
|
37
|
-
**Follow the add
|
|
37
|
+
**Follow the todo-add workflow** from `@~/.claude/pan-wizard-core/workflows/todo-add.md`.
|
|
38
38
|
|
|
39
39
|
The workflow handles all logic including:
|
|
40
40
|
1. Directory ensuring
|
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
<objective>
|
|
14
14
|
List all pending todos, allow selection, load full context for the selected todo, and route to appropriate action.
|
|
15
15
|
|
|
16
|
-
Routes to the check
|
|
16
|
+
Routes to the todo-check workflow which handles:
|
|
17
17
|
- Todo counting and listing with area filtering
|
|
18
18
|
- Interactive selection with full context loading
|
|
19
19
|
- Roadmap correlation checking
|
|
@@ -32,7 +32,7 @@ Todo state and roadmap correlation are loaded in-workflow using `init todos` and
|
|
|
32
32
|
</context>
|
|
33
33
|
|
|
34
34
|
<process>
|
|
35
|
-
**Follow the check
|
|
35
|
+
**Follow the todo-check workflow** from `@~/.claude/pan-wizard-core/workflows/todo-check.md`.
|
|
36
36
|
|
|
37
37
|
The workflow handles all logic including:
|
|
38
38
|
1. Todo existence checking
|
|
@@ -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
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pan-wizard",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.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
|
-
|
|
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');
|
|
@@ -10,6 +10,8 @@ const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, planningR
|
|
|
10
10
|
const { estimateTokens } = require('./context-budget.cjs');
|
|
11
11
|
const { collectPhaseSummaries, readErrorPatterns, appendErrorPattern, appendSessionSummary, parseLearnings, formatLearningEntry, cmdLearningsExtract, cmdLearningsList, cmdLearningsPrune } = require('./commands-learnings.cjs');
|
|
12
12
|
const { planningRootRel } = require('./planning-root.cjs');
|
|
13
|
+
const { verificationFreshness } = require('./verify-scope.cjs');
|
|
14
|
+
const { isVerificationFile } = require('./constants.cjs');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Generate a URL-safe slug from text by lowercasing and replacing non-alphanumeric chars.
|
|
@@ -631,6 +633,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
631
633
|
const milestone = getMilestoneInfo(cwd);
|
|
632
634
|
|
|
633
635
|
const phases = [];
|
|
636
|
+
const verifiedPhases = []; // indexes into phases[] of phases with a verification file
|
|
634
637
|
let totalPlans = 0;
|
|
635
638
|
let totalSummaries = 0;
|
|
636
639
|
|
|
@@ -661,12 +664,25 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
661
664
|
else if (summaryCount > 0) status = 'In Progress';
|
|
662
665
|
else status = 'Planned';
|
|
663
666
|
|
|
667
|
+
if (phaseFiles.some(isVerificationFile)) verifiedPhases.push(phases.length);
|
|
664
668
|
phases.push({ number: phaseNum, name: phaseName, plans: planCount, summaries: summaryCount, status });
|
|
665
669
|
}
|
|
666
670
|
} catch {
|
|
667
671
|
// Phases directory does not exist or is unreadable; return empty progress
|
|
668
672
|
}
|
|
669
673
|
|
|
674
|
+
// A verified phase whose covered files changed since the verified commit is
|
|
675
|
+
// stale (market-ideas M27). Not for the bar or the health score: the statusline
|
|
676
|
+
// calls the bar, which never shows it, and each check costs a git call.
|
|
677
|
+
if (format !== 'bar' && format !== 'health') {
|
|
678
|
+
for (const i of verifiedPhases) {
|
|
679
|
+
const f = verificationFreshness(cwd, phases[i].number);
|
|
680
|
+
if (f.error) continue;
|
|
681
|
+
phases[i].verification = f.state;
|
|
682
|
+
if (f.state === 'stale') phases[i].changed_since_verification = f.changed_since;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
670
686
|
const percent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
|
|
671
687
|
|
|
672
688
|
if (format === 'health') {
|
|
@@ -682,7 +698,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
682
698
|
rendered += '| Phase | Name | Plans | Status |\n';
|
|
683
699
|
rendered += '|-------|------|-------|--------|\n';
|
|
684
700
|
for (const phase of phases) {
|
|
685
|
-
rendered += `| ${phase.number} | ${phase.name} | ${phase.summaries}/${phase.plans} | ${phase.status} |\n`;
|
|
701
|
+
rendered += `| ${phase.number} | ${phase.name} | ${phase.summaries}/${phase.plans} | ${phase.status}${phase.verification === 'stale' ? ' (verification stale)' : ''} |\n`;
|
|
686
702
|
}
|
|
687
703
|
output({ rendered }, raw, rendered);
|
|
688
704
|
} else if (format === 'bar') {
|
|
@@ -1027,9 +1043,85 @@ function shouldSkipTests(files) {
|
|
|
1027
1043
|
return files.every(f => /\.md$/i.test(f));
|
|
1028
1044
|
}
|
|
1029
1045
|
|
|
1046
|
+
// ─── version (market-ideas M29) ──────────────────────────────────────────────
|
|
1047
|
+
// The version of the PAN core that is running, where it lives, and, with --check,
|
|
1048
|
+
// the update state the SessionStart update check last recorded. Reads only local
|
|
1049
|
+
// files: the check itself (`npm view`, up to 10 s) stays in the hook's background
|
|
1050
|
+
// child, so `version --check` never waits on the network and never throws offline.
|
|
1051
|
+
|
|
1052
|
+
// Where each install directory's update-check hook writes its cache: the hook's
|
|
1053
|
+
// `join(homeDir, '.claude', 'cache')` is rewritten to the runtime's GLOBAL config dir
|
|
1054
|
+
// (getConfigDirFromHome(runtime, true) in the installer).
|
|
1055
|
+
const UPDATE_CACHE_HOME_DIR = {
|
|
1056
|
+
'.claude': ['.claude'], '.codex': ['.codex'], '.gemini': ['.gemini'],
|
|
1057
|
+
'.opencode': ['.config', 'opencode'], opencode: ['.config', 'opencode'],
|
|
1058
|
+
'.github': ['.copilot'], '.copilot': ['.copilot'],
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
/** The running core's version, its directory, and the install directory holding it. */
|
|
1062
|
+
function panVersionInfo(coreDir = path.resolve(__dirname, '..', '..')) {
|
|
1063
|
+
let version = null;
|
|
1064
|
+
// VERSION is written by the installer; a source checkout has the package.json instead.
|
|
1065
|
+
try { version = fs.readFileSync(path.join(coreDir, 'VERSION'), 'utf8').trim() || null; } catch { /* not an install */ }
|
|
1066
|
+
if (!version) {
|
|
1067
|
+
try { version = JSON.parse(fs.readFileSync(path.join(coreDir, '..', 'package.json'), 'utf8')).version || null; } catch { /* neither */ }
|
|
1068
|
+
}
|
|
1069
|
+
return { version, core: toPosix(coreDir), install_dir: path.basename(path.dirname(coreDir)) };
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/** The update-check cache for this install's runtime, else the newest one any runtime wrote. */
|
|
1073
|
+
function readUpdateCache(homeDir, installDir) {
|
|
1074
|
+
const read = (segments) => {
|
|
1075
|
+
const file = path.join(homeDir, ...segments, 'cache', 'pan-update-check.json');
|
|
1076
|
+
try { return { file: toPosix(file), data: JSON.parse(fs.readFileSync(file, 'utf8')) }; } catch { return null; }
|
|
1077
|
+
};
|
|
1078
|
+
const own = UPDATE_CACHE_HOME_DIR[installDir] ? read(UPDATE_CACHE_HOME_DIR[installDir]) : null;
|
|
1079
|
+
if (own) return own;
|
|
1080
|
+
const seen = new Set();
|
|
1081
|
+
const all = [];
|
|
1082
|
+
for (const segs of Object.values(UPDATE_CACHE_HOME_DIR)) {
|
|
1083
|
+
const key = segs.join('/');
|
|
1084
|
+
if (seen.has(key)) continue;
|
|
1085
|
+
seen.add(key);
|
|
1086
|
+
const c = read(segs);
|
|
1087
|
+
if (c) all.push(c);
|
|
1088
|
+
}
|
|
1089
|
+
all.sort((a, b) => (Number(b.data.checked) || 0) - (Number(a.data.checked) || 0));
|
|
1090
|
+
return all[0] || null;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* The update state from a cache record: `unchecked` (no check has run), `offline`
|
|
1095
|
+
* (the last check could not reach npm), `available`, `current`, or `ahead` (this
|
|
1096
|
+
* core is newer than npm's latest, e.g. a prerelease or a source checkout).
|
|
1097
|
+
*/
|
|
1098
|
+
function updateState(version, cache) {
|
|
1099
|
+
if (!cache) return { state: 'unchecked', latest: null, checked_at: null, cache: null };
|
|
1100
|
+
const checkedAt = Number(cache.data.checked) ? new Date(Number(cache.data.checked) * 1000).toISOString() : null;
|
|
1101
|
+
const latest = cache.data.latest && cache.data.latest !== 'unknown' ? String(cache.data.latest) : null;
|
|
1102
|
+
if (!latest) return { state: 'offline', latest: null, checked_at: checkedAt, cache: cache.file };
|
|
1103
|
+
const { compareVersions } = require('./hygiene.cjs');
|
|
1104
|
+
const cmp = version ? compareVersions(version, latest) : -1;
|
|
1105
|
+
return { state: cmp < 0 ? 'available' : cmp > 0 ? 'ahead' : 'current', latest, checked_at: checkedAt, cache: cache.file };
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function cmdVersion(_cwd, opts, raw) {
|
|
1109
|
+
const info = panVersionInfo();
|
|
1110
|
+
const result = { version: info.version, core: info.core, install_dir: info.install_dir, node: process.version };
|
|
1111
|
+
if (opts.check) result.update = updateState(info.version, readUpdateCache(require('os').homedir(), info.install_dir));
|
|
1112
|
+
const text = opts.check
|
|
1113
|
+
? `${info.version || 'unknown'} (${result.update.state}${result.update.latest ? `, npm latest ${result.update.latest}` : ''})`
|
|
1114
|
+
: String(info.version || 'unknown');
|
|
1115
|
+
output(result, raw, text);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1030
1118
|
// ---- Error patterns, session history, learnings — extracted to commands-learnings.cjs (re-exported below)
|
|
1031
1119
|
|
|
1032
1120
|
module.exports = {
|
|
1121
|
+
cmdVersion,
|
|
1122
|
+
panVersionInfo,
|
|
1123
|
+
readUpdateCache,
|
|
1124
|
+
updateState,
|
|
1033
1125
|
cmdGenerateSlug,
|
|
1034
1126
|
cmdCurrentTimestamp,
|
|
1035
1127
|
cmdListTodos,
|