pan-wizard 3.33.0 → 3.35.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/README.md +29 -26
- package/agents/pan-conductor.md +18 -17
- package/agents/pan-counterfactual.md +2 -2
- package/agents/pan-debugger.md +30 -9
- package/agents/pan-design-checker.md +2 -1
- package/agents/pan-designer.md +2 -2
- package/agents/pan-distiller.md +3 -3
- package/agents/pan-document_code.md +14 -14
- package/agents/pan-executor.md +30 -9
- package/agents/pan-experiment-runner.md +9 -8
- package/agents/pan-hardener.md +1 -1
- package/agents/pan-integration-checker.md +2 -2
- package/agents/pan-knowledge.md +1 -1
- package/agents/pan-meta-reviewer.md +4 -4
- package/agents/pan-optimizer.md +39 -43
- package/agents/pan-phase-researcher.md +10 -10
- package/agents/pan-plan-checker.md +37 -34
- package/agents/pan-planner.md +57 -44
- package/agents/pan-previewer.md +1 -1
- package/agents/pan-project-researcher.md +12 -12
- package/agents/pan-release.md +1 -1
- package/agents/pan-research-synthesizer.md +3 -3
- package/agents/pan-reviewer.md +26 -3
- package/agents/pan-roadmapper.md +15 -14
- package/agents/pan-verifier.md +33 -25
- package/bin/install-lib.cjs +176 -38
- package/bin/install.js +81 -63
- package/commands/pan/army.md +15 -11
- package/commands/pan/assumptions.md +2 -2
- package/commands/pan/audit-deployment.md +11 -12
- package/commands/pan/cost.md +8 -8
- package/commands/pan/dashboard.md +0 -3
- package/commands/pan/debug.md +10 -7
- package/commands/pan/design-phase.md +5 -5
- package/commands/pan/discuss-phase.md +2 -0
- package/commands/pan/exec-phase.md +21 -37
- package/commands/pan/experiment.md +9 -8
- package/commands/pan/focus-auto.md +22 -20
- package/commands/pan/focus-design.md +7 -6
- package/commands/pan/focus-doc-audit.md +4 -3
- package/commands/pan/focus-drift-walking.md +4 -3
- package/commands/pan/focus-exec.md +6 -6
- package/commands/pan/focus-plan.md +12 -11
- package/commands/pan/focus-scan.md +5 -3
- package/commands/pan/focus-sync.md +3 -2
- package/commands/pan/git.md +8 -8
- package/commands/pan/health.md +2 -2
- package/commands/pan/hud.md +4 -7
- package/commands/pan/hygiene.md +14 -7
- package/commands/pan/knowledge.md +9 -10
- package/commands/pan/learn.md +10 -7
- package/commands/pan/links.md +4 -1
- package/commands/pan/map-codebase.md +1 -1
- package/commands/pan/mcp-bridge.md +19 -21
- package/commands/pan/milestone-done.md +6 -3
- package/commands/pan/milestone-new.md +1 -1
- package/commands/pan/new-project.md +10 -10
- package/commands/pan/optimize.md +18 -14
- package/commands/pan/patches.md +2 -6
- package/commands/pan/pause.md +1 -1
- package/commands/pan/phase-tests.md +3 -2
- package/commands/pan/plan-phase.md +9 -19
- package/commands/pan/preview.md +2 -3
- package/commands/pan/profile.md +2 -2
- package/commands/pan/quick.md +1 -1
- package/commands/pan/remove-phase.md +1 -1
- package/commands/pan/report.md +3 -7
- package/commands/pan/research-phase.md +16 -15
- package/commands/pan/resume.md +9 -11
- package/commands/pan/retro.md +5 -3
- package/commands/pan/review-deep.md +7 -9
- package/commands/pan/settings.md +1 -1
- package/commands/pan/verify-phase.md +0 -11
- package/commands/pan/what-if.md +3 -4
- package/hooks/dist/pan-context-monitor.js +352 -44
- package/hooks/dist/pan-cost-logger.js +30 -0
- package/hooks/dist/pan-guard.js +244 -0
- package/hooks/dist/pan-statusline.js +73 -24
- package/hooks/dist/pan-stop-guard.js +7 -2
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +44 -6
- package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
- package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
- package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
- package/pan-wizard-core/bin/lib/commands.cjs +57 -6
- package/pan-wizard-core/bin/lib/config.cjs +7 -1
- package/pan-wizard-core/bin/lib/constants.cjs +18 -6
- package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
- package/pan-wizard-core/bin/lib/core.cjs +53 -19
- package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
- package/pan-wizard-core/bin/lib/cost.cjs +142 -13
- package/pan-wizard-core/bin/lib/distill.cjs +6 -3
- package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
- package/pan-wizard-core/bin/lib/findings.cjs +135 -21
- package/pan-wizard-core/bin/lib/focus.cjs +13 -4
- package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
- package/pan-wizard-core/bin/lib/hygiene.cjs +184 -17
- package/pan-wizard-core/bin/lib/init.cjs +13 -4
- package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
- package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
- package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
- package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
- package/pan-wizard-core/bin/lib/memory.cjs +508 -54
- package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
- package/pan-wizard-core/bin/lib/optimize.cjs +97 -17
- package/pan-wizard-core/bin/lib/phase-gate.cjs +174 -0
- package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
- package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
- package/pan-wizard-core/bin/lib/phase.cjs +39 -13
- package/pan-wizard-core/bin/lib/preview.cjs +34 -31
- package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
- package/pan-wizard-core/bin/lib/runner.cjs +2 -1
- package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
- package/pan-wizard-core/bin/lib/state.cjs +43 -28
- package/pan-wizard-core/bin/lib/template.cjs +11 -4
- package/pan-wizard-core/bin/lib/verdict.cjs +140 -10
- package/pan-wizard-core/bin/lib/verify-integrity.cjs +181 -0
- package/pan-wizard-core/bin/lib/verify-plan-set.cjs +230 -0
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
- package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
- package/pan-wizard-core/bin/lib/verify-scope.cjs +11 -7
- package/pan-wizard-core/bin/lib/verify.cjs +30 -28
- package/pan-wizard-core/bin/lib/whatif.cjs +53 -9
- package/pan-wizard-core/bin/pan-tools.cjs +114 -19
- package/pan-wizard-core/learnings/index.json +5 -5
- package/pan-wizard-core/mcp/merge-gate.cjs +14 -3
- package/pan-wizard-core/mcp/native-tools.cjs +3 -3
- package/pan-wizard-core/opencode/pan-wizard.js +139 -0
- package/pan-wizard-core/references/checkpoints.md +3 -3
- package/pan-wizard-core/references/continuation-format.md +3 -3
- package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
- package/pan-wizard-core/references/design-methodology.md +1 -1
- package/pan-wizard-core/references/git-integration.md +4 -6
- package/pan-wizard-core/references/git-planning-commit.md +2 -2
- package/pan-wizard-core/references/guardrails.md +2 -2
- package/pan-wizard-core/references/handoff-decisions.md +3 -3
- package/pan-wizard-core/references/model-profile-resolution.md +5 -5
- package/pan-wizard-core/references/model-profiles.md +15 -10
- package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
- package/pan-wizard-core/references/planning-config.md +7 -7
- package/pan-wizard-core/references/tdd.md +10 -1
- package/pan-wizard-core/references/verdict-contract.md +6 -1
- package/pan-wizard-core/references/verification-patterns.md +5 -5
- package/pan-wizard-core/templates/codebase/architecture.md +2 -2
- package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
- package/pan-wizard-core/templates/codebase/concerns.md +3 -3
- package/pan-wizard-core/templates/codebase/integrations.md +6 -6
- package/pan-wizard-core/templates/codebase/relationships.md +2 -2
- package/pan-wizard-core/templates/codebase/stack.md +2 -2
- package/pan-wizard-core/templates/codebase/structure.md +4 -4
- package/pan-wizard-core/templates/codebase/testing.md +4 -4
- package/pan-wizard-core/templates/config.json +2 -6
- package/pan-wizard-core/templates/continue-here.md +1 -1
- package/pan-wizard-core/templates/debug-subagent-prompt.md +5 -3
- package/pan-wizard-core/templates/debug.md +15 -1
- package/pan-wizard-core/templates/milestone-archive.md +6 -6
- package/pan-wizard-core/templates/milestone.md +1 -1
- package/pan-wizard-core/templates/phase-prompt.md +31 -17
- package/pan-wizard-core/templates/planner-subagent-prompt.md +5 -5
- package/pan-wizard-core/templates/preview-report.md +1 -1
- package/pan-wizard-core/templates/research-project/architecture.md +3 -3
- package/pan-wizard-core/templates/research-project/features.md +1 -1
- package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
- package/pan-wizard-core/templates/research-project/stack.md +3 -3
- package/pan-wizard-core/templates/research.md +5 -5
- package/pan-wizard-core/templates/retrospective.md +1 -1
- package/pan-wizard-core/templates/roadmap.md +15 -15
- package/pan-wizard-core/templates/state.md +4 -4
- package/pan-wizard-core/templates/summary-complex.md +1 -0
- package/pan-wizard-core/templates/summary-minimal.md +1 -0
- package/pan-wizard-core/templates/summary-standard.md +2 -1
- package/pan-wizard-core/templates/summary.md +18 -2
- package/pan-wizard-core/templates/uat.md +3 -3
- package/pan-wizard-core/templates/user-setup.md +8 -8
- package/pan-wizard-core/templates/verification-report.md +53 -8
- package/pan-wizard-core/workflows/add-phase.md +1 -1
- package/pan-wizard-core/workflows/assumptions.md +1 -1
- package/pan-wizard-core/workflows/diagnose-issues.md +4 -4
- package/pan-wizard-core/workflows/discuss-phase.md +10 -8
- package/pan-wizard-core/workflows/exec-phase.md +75 -95
- package/pan-wizard-core/workflows/execute-plan.md +8 -7
- package/pan-wizard-core/workflows/health.md +13 -12
- package/pan-wizard-core/workflows/help.md +8 -9
- package/pan-wizard-core/workflows/learn.md +13 -11
- package/pan-wizard-core/workflows/map-codebase.md +10 -10
- package/pan-wizard-core/workflows/milestone-audit.md +10 -10
- package/pan-wizard-core/workflows/milestone-gaps.md +18 -25
- package/pan-wizard-core/workflows/milestone-new.md +7 -6
- package/pan-wizard-core/workflows/new-project.md +17 -25
- package/pan-wizard-core/workflows/optimize.md +11 -9
- package/pan-wizard-core/workflows/pause.md +6 -1
- package/pan-wizard-core/workflows/phase-tests.md +4 -4
- package/pan-wizard-core/workflows/plan-phase.md +53 -42
- package/pan-wizard-core/workflows/profile.md +2 -2
- package/pan-wizard-core/workflows/progress.md +6 -8
- package/pan-wizard-core/workflows/quick.md +7 -5
- package/pan-wizard-core/workflows/remove-phase.md +1 -1
- package/pan-wizard-core/workflows/research-phase.md +9 -5
- package/pan-wizard-core/workflows/resume-project.md +4 -6
- package/pan-wizard-core/workflows/retro.md +3 -1
- package/pan-wizard-core/workflows/settings.md +16 -24
- package/pan-wizard-core/workflows/todo-check.md +4 -3
- package/pan-wizard-core/workflows/transition.md +16 -12
- package/pan-wizard-core/workflows/update.md +25 -17
- package/pan-wizard-core/workflows/verify-phase.md +22 -20
- package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
- package/pan-zcode/README.md +7 -7
- package/pan-zcode/bin/install-zcode.js +1 -1
- package/scripts/build-hooks.js +1 -0
- package/scripts/build-plugin.js +3 -1
- package/scripts/generate-skills-docs.py +15 -6
- package/scripts/prompt-lint.cjs +167 -0
- package/scripts/test-surface.cjs +1 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
1. `.planning/state.md`
|
|
6
6
|
2. `.planning/project.md`
|
|
7
|
-
3. `.planning/roadmap.md`
|
|
7
|
+
3. The current phase's roadmap slice (`.planning/phases/XX-current/XX-roadmap-slice.md`) — not the whole roadmap.md (Route A takes the next phase from `phase complete` and `roadmap get-phase`)
|
|
8
8
|
4. Current phase's plan files (`*-plan.md`)
|
|
9
9
|
5. Current phase's summary files (`*-summary.md`)
|
|
10
10
|
|
|
@@ -152,9 +152,11 @@ If found, delete them — phase is complete, handoffs are stale.
|
|
|
152
152
|
**Delegate roadmap.md and state.md updates to pan-tools:**
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
|
-
TRANSITION=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs phase complete "${current_phase}")
|
|
155
|
+
TRANSITION=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs phase complete "${current_phase}") # skip when exec-phase invoked this workflow: its `phase complete` already ran — reuse that result
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
`phase complete` runs the phase gate first. Exit 1 with `error: phase_gate_refused` (`workflow.phase_gate: enforce`) means the phase was not marked complete: show `gate.reasons` and stop the transition. With `gate.ok` false under `warn`, the phase was marked complete: show the reasons as warnings before continuing.
|
|
159
|
+
|
|
158
160
|
The CLI handles:
|
|
159
161
|
- Marking the phase checkbox as `[x]` complete with today's date
|
|
160
162
|
- Updating plan count to final (e.g., "3/3 plans complete")
|
|
@@ -358,9 +360,9 @@ Then Edit state.md (still under the P-1604 batched-write policy — fold this Ed
|
|
|
358
360
|
**Format:**
|
|
359
361
|
|
|
360
362
|
```markdown
|
|
361
|
-
Last session
|
|
362
|
-
Stopped
|
|
363
|
-
Resume
|
|
363
|
+
**Last session:** [today]
|
|
364
|
+
**Stopped At:** Phase [X] complete, ready to plan Phase [X+1]
|
|
365
|
+
**Resume File:** None
|
|
364
366
|
```
|
|
365
367
|
|
|
366
368
|
**Step complete when:**
|
|
@@ -396,12 +398,12 @@ This returns all phases with goals, disk status, and completion info.
|
|
|
396
398
|
|
|
397
399
|
**Route A: More phases remain in milestone**
|
|
398
400
|
|
|
399
|
-
|
|
401
|
+
Take the name from `next_phase_name` (the `phase complete` result) and the goal from `roadmap get-phase "${next_phase}"`, not the whole roadmap.md.
|
|
400
402
|
|
|
401
403
|
**Check if next phase has context.md:**
|
|
402
404
|
|
|
403
405
|
```bash
|
|
404
|
-
|
|
406
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init phase-op "${next_phase}" # has_context is true when the next phase has a context.md
|
|
405
407
|
```
|
|
406
408
|
|
|
407
409
|
**If next phase exists:**
|
|
@@ -448,11 +450,12 @@ Task(
|
|
|
448
450
|
<instructions>
|
|
449
451
|
1. Read plan-phase.md from execution_context for your complete workflow.
|
|
450
452
|
2. Follow ALL steps: initialize, validate_phase, load context.md, handle research (P-1401 bypass if applicable), spawn pan-planner, optionally spawn pan-plan-checker, then auto-advance to exec-phase via Task (per plan-phase.md step 14).
|
|
451
|
-
3.
|
|
452
|
-
4. Do NOT use the Skill tool or /pan: commands. Do NOT exit early —
|
|
453
|
+
3. plan-phase step 14 spawns exec-phase with --no-transition, so exec-phase returns without running transition.md and the chain ends there: on PHASE COMPLETE plan-phase prints `/pan:discuss-phase {next} --auto` as the next step; on GAPS FOUND it stops and reports the gaps.
|
|
454
|
+
4. Do NOT use the Skill tool or /pan: commands. Do NOT exit early — wait for the exec-phase Task to return.
|
|
453
455
|
</instructions>
|
|
454
456
|
",
|
|
455
457
|
subagent_type="general-purpose",
|
|
458
|
+
model="$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs resolve-model pan-conductor --raw)",
|
|
456
459
|
description="Plan Phase ${NEXT_PHASE}"
|
|
457
460
|
)
|
|
458
461
|
```
|
|
@@ -495,12 +498,13 @@ Task(
|
|
|
495
498
|
</instructions>
|
|
496
499
|
",
|
|
497
500
|
subagent_type="general-purpose",
|
|
501
|
+
model="$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs resolve-model pan-conductor --raw)",
|
|
498
502
|
description="Discuss Phase ${NEXT_PHASE}"
|
|
499
503
|
)
|
|
500
504
|
```
|
|
501
505
|
|
|
502
506
|
**Handle next-phase Task return:**
|
|
503
|
-
- **PHASE COMPLETE** (the spawned chain reached verification + roadmap update for ${NEXT_PHASE} and
|
|
507
|
+
- **PHASE COMPLETE** (the spawned chain reached verification + roadmap update for ${NEXT_PHASE} and stopped at that phase's boundary; plan-phase printed `/pan:discuss-phase {next} --auto`) → Done. Workflow chain finished.
|
|
504
508
|
- **GAPS FOUND / FAILED / TIMEOUT** → Display the failure, stop the recursion, return status to user. Do NOT attempt to skip ahead.
|
|
505
509
|
|
|
506
510
|
</if>
|
|
@@ -574,10 +578,10 @@ Phase {X} marked complete.
|
|
|
574
578
|
|
|
575
579
|
🎉 Milestone {version} is 100% complete — all {N} phases finished!
|
|
576
580
|
|
|
577
|
-
|
|
581
|
+
Auto-advance stops at the milestone boundary. Next: /pan:milestone-done {version}
|
|
578
582
|
```
|
|
579
583
|
|
|
580
|
-
|
|
584
|
+
Stop here and show `/pan:milestone-done {version}` as the next step: the milestone boundary ends the auto chain (auto-advance was cleared above).
|
|
581
585
|
|
|
582
586
|
</if>
|
|
583
587
|
|
|
@@ -24,19 +24,32 @@ case "$VERSION_FILE" in
|
|
|
24
24
|
./*) SCOPE="LOCAL" ;;
|
|
25
25
|
*) SCOPE="GLOBAL" ;;
|
|
26
26
|
esac
|
|
27
|
+
# The same templated path names the runtime: the installer rewrites ~/.claude/ to
|
|
28
|
+
# this runtime's own directory. With no runtime flag the installer installs Claude
|
|
29
|
+
# Code only, so the flag must be passed.
|
|
30
|
+
case "$VERSION_FILE" in
|
|
31
|
+
*.codex/*) RUNTIME="--codex" ;;
|
|
32
|
+
*.gemini/*) RUNTIME="--gemini" ;;
|
|
33
|
+
*opencode/*) RUNTIME="--opencode" ;;
|
|
34
|
+
*.github/*|*.copilot/*) RUNTIME="--copilot" ;;
|
|
35
|
+
*) RUNTIME="--claude" ;;
|
|
36
|
+
esac
|
|
27
37
|
|
|
28
38
|
if [ -f "$VERSION_FILE" ] && [ -f "$MARKER_FILE" ] && grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+' "$VERSION_FILE"; then
|
|
29
39
|
cat "$VERSION_FILE"
|
|
40
|
+
echo "$RUNTIME"
|
|
30
41
|
echo "$SCOPE"
|
|
31
42
|
else
|
|
32
43
|
echo "UNKNOWN"
|
|
44
|
+
echo "$RUNTIME"
|
|
45
|
+
echo "$SCOPE"
|
|
33
46
|
fi
|
|
34
47
|
```
|
|
35
48
|
|
|
36
|
-
Parse output (there is ONE install; scope
|
|
37
|
-
- If last line is "LOCAL": this install is local-scoped (templated prefix is `./`-relative);
|
|
38
|
-
- If last line is "GLOBAL": this install is global-scoped (templated prefix is an absolute path);
|
|
39
|
-
- If "UNKNOWN": the VERSION/marker files are missing or invalid; proceed to install step (treat as version 0.0.0)
|
|
49
|
+
Parse output (there is ONE install; scope and runtime are derived from the single templated prefix). The output is three lines: the installed version (or `UNKNOWN`), the runtime flag (`--claude`, `--codex`, `--gemini`, `--opencode` or `--copilot`) and the scope. Remember the runtime flag: the install step writes it into its command.
|
|
50
|
+
- If last line is "LOCAL": this install is local-scoped (templated prefix is `./`-relative); use `--local`
|
|
51
|
+
- If last line is "GLOBAL": this install is global-scoped (templated prefix is an absolute path); use `--global`
|
|
52
|
+
- If the first line is "UNKNOWN": the VERSION/marker files are missing or invalid; proceed to install step (treat as version 0.0.0) — the runtime and scope lines still apply
|
|
40
53
|
|
|
41
54
|
**If VERSION file missing:**
|
|
42
55
|
```
|
|
@@ -63,7 +76,7 @@ npm view pan-wizard version 2>/dev/null
|
|
|
63
76
|
```
|
|
64
77
|
Couldn't check for updates (offline or npm unavailable).
|
|
65
78
|
|
|
66
|
-
To update manually: `npx pan-wizard --global`
|
|
79
|
+
To update manually: `npx -y pan-wizard@latest {runtime_flag} --global` (`--local` for a local install), with the runtime flag and scope step 1 printed
|
|
67
80
|
```
|
|
68
81
|
|
|
69
82
|
Exit.
|
|
@@ -155,28 +168,23 @@ Run the update using the install type detected in step 1:
|
|
|
155
168
|
|
|
156
169
|
**If LOCAL install:**
|
|
157
170
|
```bash
|
|
158
|
-
npx -y pan-wizard@latest --local
|
|
171
|
+
npx -y pan-wizard@latest {runtime_flag} --local
|
|
159
172
|
```
|
|
160
173
|
|
|
161
|
-
**If GLOBAL install
|
|
174
|
+
**If GLOBAL install:**
|
|
162
175
|
```bash
|
|
163
|
-
npx -y pan-wizard@latest --global
|
|
176
|
+
npx -y pan-wizard@latest {runtime_flag} --global
|
|
164
177
|
```
|
|
165
178
|
|
|
166
|
-
|
|
179
|
+
`{runtime_flag}` is the runtime line step 1 printed: write the flag itself into the command. A variable set in step 1's shell call is gone in this one (each Bash call starts a new shell), and an empty `""` argument in its place installs Claude Code only. A `--config-dir` or `--unified-skills` install is not recognised from its path: for those, re-run the installer with your original flags instead.
|
|
167
180
|
|
|
168
|
-
|
|
181
|
+
Capture output. If install fails, show error and exit.
|
|
169
182
|
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
rm -f ~/.claude/cache/pan-update-check.json
|
|
173
|
-
```
|
|
183
|
+
Clear the update cache so the statusline indicator disappears. The update-check hook keeps it under your home directory for every install, local or global, in the runtime's own config directory: `.claude` (Claude Code), `.codex` (Codex), `.gemini` (Gemini CLI) or `.copilot` (Copilot CLI); OpenCode runs no update-check hook, so there is nothing to clear there:
|
|
174
184
|
|
|
175
|
-
**If GLOBAL install:**
|
|
176
185
|
```bash
|
|
177
|
-
rm -f
|
|
186
|
+
rm -f "$HOME/<config dir from the list above>/cache/pan-update-check.json"
|
|
178
187
|
```
|
|
179
|
-
(Paths are templated at install time for runtime compatibility)
|
|
180
188
|
</step>
|
|
181
189
|
|
|
182
190
|
<step name="display_result">
|
|
@@ -22,7 +22,7 @@ Then verify each level against the actual codebase.
|
|
|
22
22
|
@~/.claude/pan-wizard-core/references/guardrails.md
|
|
23
23
|
@~/.claude/pan-wizard-core/templates/verification-report.md
|
|
24
24
|
|
|
25
|
-
> **Also see:** `~/.claude/pan-wizard-core/learnings/universal/` — AI-derived patterns from prior experiments. **Don't skim the whole folder.** Run `pan-tools learn topics-for --agent verifier --token-budget 5000 --raw` to load
|
|
25
|
+
> **Also see:** `~/.claude/pan-wizard-core/learnings/universal/` — AI-derived patterns from prior experiments. **Don't skim the whole folder.** Run `pan-tools learn topics-for --agent verifier --cue "<phase goal; the files it touches>" --token-budget 5000 --raw` to load the topics that match this task, within the budget; if none matches, it falls back to the topics tagged relevant for verification. Per P-RES-002 (distractor-density research), reading every topic degrades reasoning even at modest token counts.
|
|
26
26
|
</required_reading>
|
|
27
27
|
|
|
28
28
|
## Re-Read Checkpoints
|
|
@@ -47,14 +47,15 @@ INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init phase-op "${PHASE_A
|
|
|
47
47
|
|
|
48
48
|
Extract from init JSON: `phase_dir`, `phase_number`, `phase_name`, `has_plans`, `plan_count`.
|
|
49
49
|
|
|
50
|
-
Then load phase details and list plans/summaries:
|
|
50
|
+
Then load phase details, write the phase's roadmap slice, and list plans/summaries:
|
|
51
51
|
```bash
|
|
52
52
|
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs roadmap get-phase "${phase_number}"
|
|
53
|
-
|
|
53
|
+
SLICE_PATH=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs roadmap slice "${phase_number}" --write --raw)
|
|
54
|
+
grep -E "^\|[^|]*\| *Phase +0*${phase_number#0} " .planning/requirements.md 2>/dev/null
|
|
54
55
|
ls "$phase_dir"/*-summary.md "$phase_dir"/*-plan.md 2>/dev/null
|
|
55
56
|
```
|
|
56
57
|
|
|
57
|
-
Extract **phase goal** from
|
|
58
|
+
Extract the **phase goal** and success criteria from `get-phase` (the outcome to verify, not tasks), and the phase's **requirements** from `$SLICE_PATH` (its `## Its requirements` lines). Do not read roadmap.md or requirements.md whole: on a long project they are the largest files in `.planning/`, and the slice carries what verification needs from them. If `SLICE_PATH` is empty, grep requirements.md for the phase's IDs instead.
|
|
58
59
|
</step>
|
|
59
60
|
|
|
60
61
|
<step name="check_prior_verification">
|
|
@@ -62,7 +63,7 @@ Extract **phase goal** from roadmap.md (the outcome to verify, not tasks) and **
|
|
|
62
63
|
|
|
63
64
|
If `phase_number` > 1:
|
|
64
65
|
```bash
|
|
65
|
-
PREV=$((phase_number - 1))
|
|
66
|
+
case "$phase_number" in *.*) PREV="${phase_number%%.*}" ;; *[A-Za-z]) PREV="${phase_number%[A-Za-z]}" ;; *) PREV=$((10#$phase_number - 1)) ;; esac
|
|
66
67
|
# Resolve the previous phase's directory via find-phase (handles zero-padding
|
|
67
68
|
# and the .planning/phases/ layout), like exec-phase does — do NOT glob
|
|
68
69
|
# .planning/phase-N*/ (wrong dir, unpadded, matches nothing). With --raw,
|
|
@@ -104,15 +105,15 @@ HAS_TEST=$(node -e "try{const p=require('./package.json');process.stdout.write(p
|
|
|
104
105
|
```bash
|
|
105
106
|
TEST_OUTPUT=$(npm test 2>&1)
|
|
106
107
|
TEST_EXIT=$?
|
|
107
|
-
TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -E "
|
|
108
|
-
TEST_PASS=$(echo "$TEST_OUTPUT" | grep -E "
|
|
109
|
-
TEST_FAIL=$(echo "$TEST_OUTPUT" | grep -E "
|
|
108
|
+
TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -E "^(ℹ|#) tests" | awk '{print $NF}')
|
|
109
|
+
TEST_PASS=$(echo "$TEST_OUTPUT" | grep -E "^(ℹ|#) pass" | awk '{print $NF}')
|
|
110
|
+
TEST_FAIL=$(echo "$TEST_OUTPUT" | grep -E "^(ℹ|#) fail" | awk '{print $NF}')
|
|
110
111
|
```
|
|
111
112
|
|
|
112
113
|
3. Evaluate results.
|
|
113
114
|
|
|
114
115
|
**Check `TEST_EXIT` FIRST, before any count.** A suite that did not run emits no
|
|
115
|
-
`ℹ fail` line at all, so `TEST_FAIL` comes back EMPTY — not `0`. Reading an empty
|
|
116
|
+
`ℹ fail` / `# fail` line at all, so `TEST_FAIL` comes back EMPTY — not `0`. Reading an empty
|
|
116
117
|
value as "no failures" scores a crashed suite as a pass, which is the worst
|
|
117
118
|
direction this gate can fail in: a phase ships green on a project whose tests never
|
|
118
119
|
executed. Empty is not zero. Judge the exit code, then the counts.
|
|
@@ -120,6 +121,7 @@ executed. Empty is not zero. Judge the exit code, then the counts.
|
|
|
120
121
|
| Condition | Action |
|
|
121
122
|
|-----------|--------|
|
|
122
123
|
| `TEST_EXIT` = 0 **and** `TEST_FAIL` = 0 (a real number) | Record counts, continue to must-haves |
|
|
124
|
+
| `TEST_EXIT` = 0 **and** no `ℹ`/`#` count lines (a runner other than Node's built-in one) | Set `test_gate_status: passed`; record the runner's own summary line as the counts |
|
|
123
125
|
| `TEST_EXIT` ≠ 0 **and** failures were reported | Set `test_gate_status: failed`, include failure details |
|
|
124
126
|
| `TEST_EXIT` ≠ 0 **and** `TEST_FAIL` is empty/absent — the suite CRASHED or could not run (syntax error, missing module, bad import, no runner) | Set `test_gate_status: failed`, and record the reason as `suite did not run`. **Never `skipped`, never `passed`.** A suite that cannot execute is stronger evidence of a broken phase than one that runs and fails |
|
|
125
127
|
| `HAS_TEST` = `no` (handled in step 1) | Record as `test_gate_status: skipped`, continue. This is the ONLY legitimate route to `skipped` |
|
|
@@ -150,13 +152,13 @@ TEST_FAILED: ${TEST_FAIL}
|
|
|
150
152
|
Use pan-tools to extract must_haves from each PLAN:
|
|
151
153
|
|
|
152
154
|
```bash
|
|
153
|
-
for plan in "$
|
|
155
|
+
for plan in "$phase_dir"/*-plan.md; do
|
|
154
156
|
MUST_HAVES=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs frontmatter get "$plan" --field must_haves)
|
|
155
157
|
echo "=== $plan ===" && echo "$MUST_HAVES"
|
|
156
158
|
done
|
|
157
159
|
```
|
|
158
160
|
|
|
159
|
-
Returns
|
|
161
|
+
Returns `{ "must_haves": { truths: [...], artifacts: [...], key_links: [...] } }`: each truth a string, each artifact an object (`path`, `provides`, …), each key link an object (`from`, `to`, `via`).
|
|
160
162
|
|
|
161
163
|
Aggregate all must_haves across plans for phase-level verification.
|
|
162
164
|
|
|
@@ -165,7 +167,7 @@ Aggregate all must_haves across plans for phase-level verification.
|
|
|
165
167
|
If no must_haves in frontmatter (MUST_HAVES returns error or empty), check for Success Criteria:
|
|
166
168
|
|
|
167
169
|
```bash
|
|
168
|
-
PHASE_DATA=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs roadmap get-phase "${phase_number}"
|
|
170
|
+
PHASE_DATA=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs roadmap get-phase "${phase_number}")
|
|
169
171
|
```
|
|
170
172
|
|
|
171
173
|
Parse the `success_criteria` array from the JSON output. If non-empty:
|
|
@@ -200,7 +202,7 @@ For each truth: identify supporting artifacts → check artifact status → chec
|
|
|
200
202
|
Use pan-tools for artifact verification against must_haves in each PLAN:
|
|
201
203
|
|
|
202
204
|
```bash
|
|
203
|
-
for plan in "$
|
|
205
|
+
for plan in "$phase_dir"/*-plan.md; do
|
|
204
206
|
ARTIFACT_RESULT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs verify artifacts "$plan")
|
|
205
207
|
echo "=== $plan ===" && echo "$ARTIFACT_RESULT"
|
|
206
208
|
done
|
|
@@ -232,7 +234,7 @@ WIRED = imported AND used. ORPHANED = exists but not imported/used.
|
|
|
232
234
|
Use pan-tools for key link verification against must_haves in each PLAN:
|
|
233
235
|
|
|
234
236
|
```bash
|
|
235
|
-
for plan in "$
|
|
237
|
+
for plan in "$phase_dir"/*-plan.md; do
|
|
236
238
|
LINKS_RESULT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs verify key-links "$plan")
|
|
237
239
|
echo "=== $plan ===" && echo "$LINKS_RESULT"
|
|
238
240
|
done
|
|
@@ -242,8 +244,8 @@ Parse JSON result: `{ all_verified, verified, total, links: [{from, to, via, ver
|
|
|
242
244
|
|
|
243
245
|
**Link status from result:**
|
|
244
246
|
- `verified=true` → WIRED
|
|
245
|
-
- `verified=false` with "not found" → NOT_WIRED
|
|
246
|
-
- `verified=false` with
|
|
247
|
+
- `verified=false` with detail "Source file not found" or "Target not referenced in source" → NOT_WIRED
|
|
248
|
+
- `verified=false` with detail `Pattern "…" not found in source or target` → PARTIAL
|
|
247
249
|
|
|
248
250
|
**Fallback patterns (if key_links not in must_haves):**
|
|
249
251
|
|
|
@@ -260,7 +262,7 @@ Record status and evidence for each key link.
|
|
|
260
262
|
<step name="verify_requirements">
|
|
261
263
|
If requirements.md exists:
|
|
262
264
|
```bash
|
|
263
|
-
grep -E "Phase
|
|
265
|
+
grep -E "^\|[^|]*\| *Phase +0*${phase_number#0} " .planning/requirements.md 2>/dev/null
|
|
264
266
|
```
|
|
265
267
|
|
|
266
268
|
For each requirement: parse description → identify supporting truths/artifacts → status: ✓ SATISFIED / ✗ BLOCKED / ? NEEDS HUMAN.
|
|
@@ -309,7 +311,7 @@ If gaps_found:
|
|
|
309
311
|
|
|
310
312
|
<step name="create_report">
|
|
311
313
|
```bash
|
|
312
|
-
REPORT_PATH="$
|
|
314
|
+
REPORT_PATH="$phase_dir/${phase_number}-verification.md"
|
|
313
315
|
VERIFIED_COMMIT=$(git rev-parse HEAD 2>/dev/null)
|
|
314
316
|
```
|
|
315
317
|
|
|
@@ -326,9 +328,9 @@ If human_needed: list items requiring human testing.
|
|
|
326
328
|
|
|
327
329
|
Orchestrator routes: `passed` → update_roadmap | `gaps_found` → create/execute fixes, re-verify | `human_needed` → present to user.
|
|
328
330
|
|
|
329
|
-
**Record the verification.** The record adds its findings to the ledger and logs the outcome to the trace (`verdict_passed`, `verdict_failed` or `verdict_needs_human`). A re-verification is the next attempt
|
|
331
|
+
**Record the verification.** The record adds its findings to the ledger and logs the outcome to the trace (`verdict_passed`, `verdict_failed` or `verdict_needs_human`). A re-verification is the next attempt. It closes the gaps it lists under `re_verification.gaps_closed`; a gap it neither reports nor answers stays open:
|
|
330
332
|
```bash
|
|
331
|
-
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings record --phase "${
|
|
333
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings record --phase "${phase_number}" --file "$REPORT_PATH" 2>/dev/null || true
|
|
332
334
|
```
|
|
333
335
|
Recording the same report twice is a no-op, so exec-phase recording it again after this workflow returns is safe.
|
|
334
336
|
</step>
|
|
@@ -11,7 +11,7 @@ hard-depend on, and the go/no-go facts that can only be settled on a real ZCode
|
|
|
11
11
|
| # | Question | If NO → fallback (already in the design) |
|
|
12
12
|
|---|----------|------------------------------------------|
|
|
13
13
|
| 1 | Can a **subagent call MCP tools**? | Primary Agent makes every `pan-mcp` call and feeds results into subagent prompts. Determinism unaffected. |
|
|
14
|
-
| 2 | Are **local stdio MCP calls metered** against the MCP-Calls quota? |
|
|
14
|
+
| 2 | Are **local stdio MCP calls metered** against the MCP-Calls quota? | The read-only aggregators (`pan://state`, `pan://phases`, `pan://progress`, `pan://health`, `pan://links`, `pan://cost`, `pan://findings`) are MCP *resources*; reads that take an argument or fail without a roadmap (`pan_find_phase`, `pan_resolve_model`, `pan_roadmap_analyze`, `pan_preview_phases`, `pan_preview_phase`) and the per-step `pan_next_action` poll are tools, which a metered quota would count; batch/cache; never make a hosted z.ai MCP load-bearing. |
|
|
15
15
|
| 3 | Exact **subagent frontmatter schema** / plugin layout / MCP config path? | Drive ZCode's own Import / Settings; treat `pan-zcode/lib/convert-agent.cjs` output as a labelled fallback, not a contract. |
|
|
16
16
|
| 4 | Any **file-based / headless** way to register an MCP server, or GUI-only? | `install-zcode.js` emits a bundle + `INSTALL-ZCODE.md`; the human finishes in ZCode's UI. |
|
|
17
17
|
| 5 | Any **lifecycle hook** that can hard-gate a spawn? | Enforce caps at the MCP-tool-call boundary (the orchestrator), not pre-spawn. |
|
|
@@ -21,7 +21,7 @@ hard-depend on, and the go/no-go facts that can only be settled on a real ZCode
|
|
|
21
21
|
- **The merge gate is not self-sufficient.** Under ZCode Full Access, a raw Bash
|
|
22
22
|
`git push` bypasses `pan_confirm_merge`. Non-bypassability rests on **server-side
|
|
23
23
|
branch protection**; the MCP gate is the second, in-process lock. `INSTALL-ZCODE.md`
|
|
24
|
-
mandates branch protection and "
|
|
24
|
+
mandates branch protection and "Never run the install/import step in ZCode Full Access mode."
|
|
25
25
|
- **No subagent nesting.** The army flattens to one delegation layer (PAN already caps
|
|
26
26
|
nesting at 2, so this is tolerable). `Task` is dropped from ported subagents.
|
|
27
27
|
- **User-global subagents only.** No repo-scoped rosters or per-project model profiles.
|
package/pan-zcode/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PAN-Z — a ZCode-native PAN subsystem
|
|
2
2
|
|
|
3
|
-
PAN-Z gives [ZCode](https://zcode.z.ai) (z.ai's GLM
|
|
3
|
+
PAN-Z gives [ZCode](https://zcode.z.ai) (z.ai's GLM coding harness) the PAN Wizard
|
|
4
4
|
workflow — the multi-phase lifecycle, the bot-army with a human merge gate, deterministic
|
|
5
5
|
state tracking — **without** cloning PAN's slash-commands or hooks (ZCode has neither).
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ ZCode through the one interface it speaks: **MCP**.
|
|
|
14
14
|
## How it fits
|
|
15
15
|
|
|
16
16
|
```text
|
|
17
|
-
ZCode harness (GLM
|
|
17
|
+
ZCode harness (GLM) primary Agent drives everything; ported subagents fan out
|
|
18
18
|
│ MCP · local stdio
|
|
19
19
|
pan-wizard-core/mcp (SHARED) a thin, zero-dep bridge — verbs → MCP tools/resources
|
|
20
20
|
│ spawn: node pan-tools.cjs <verb> --cwd <root>
|
|
@@ -28,13 +28,13 @@ registration pointing at that shared path. **Never fork a copy back under `pan-z
|
|
|
28
28
|
protocol layer, many consumers, or the two drift the way the per-runtime command trees did
|
|
29
29
|
before ADR-0028.
|
|
30
30
|
|
|
31
|
-
**Scope boundary (by design):** the bridge exposes `pan-tools` verbs as MCP tools/resources and nothing more. It intentionally does **not** carry rich agent *session state* — diffs, streaming, live thread lifecycle — because MCP can't faithfully represent it (the reason OpenAI built the Codex harness as a native Rust core rather than over MCP). Keep the bridge to tool/resource exposure; the CLI's JSON contract is the tool contract. See `KNOWN-BETA-RISKS.md`.
|
|
31
|
+
**Scope boundary (by design):** the bridge exposes `pan-tools` verbs as MCP tools/resources, plus the in-process `next-action` and merge-gate tools (M2), and nothing more. It intentionally does **not** carry rich agent *session state* — diffs, streaming, live thread lifecycle — because MCP can't faithfully represent it (the reason OpenAI built the Codex harness as a native Rust core rather than over MCP). Keep the bridge to tool/resource exposure; the CLI's JSON contract is the tool contract. See `KNOWN-BETA-RISKS.md`.
|
|
32
32
|
|
|
33
33
|
## Status — M1–M5 built (M0 is the human verify spike)
|
|
34
34
|
|
|
35
35
|
- **M1 — bridge core.** `pan-wizard-core/mcp/tool-registry.cjs` (pure verb→tool/resource map, with a hard
|
|
36
36
|
guardrail against exposing a force/reset/rebase/push verb) + `pan-wizard-core/mcp/server.cjs` (a
|
|
37
|
-
**zero-dependency** JSON-RPC 2.0 stdio MCP server;
|
|
37
|
+
**zero-dependency** JSON-RPC 2.0 stdio MCP server; read-only aggregators readable on any project → resources; reads that take an argument or fail without a roadmap, and actions → tools with
|
|
38
38
|
accurate hints; shell-less `execFile` spawn; `@file:` overflow protocol; strict per-arg
|
|
39
39
|
validation). **Dual-era** per the MCP 2026-07-28 stateless spec (ADR-0041): legacy clients
|
|
40
40
|
use the `initialize` handshake; modern clients declare their protocol version in each
|
|
@@ -73,6 +73,6 @@ implemented.
|
|
|
73
73
|
## Zero dependencies
|
|
74
74
|
|
|
75
75
|
Like the rest of PAN, this subsystem ships **no runtime dependencies**. The MCP protocol is
|
|
76
|
-
implemented directly
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
implemented directly, in the shared bridge under `pan-wizard-core/mcp/`, rather than via an
|
|
77
|
+
SDK; `pan-zcode/` holds no protocol code of its own, so a dependency added here would not
|
|
78
|
+
reach the bridge.
|
|
@@ -38,7 +38,7 @@ PAN-Z bridges PAN's engine to ZCode over MCP. Finish setup inside ZCode:
|
|
|
38
38
|
|
|
39
39
|
1. **Register the MCP server.** Add the contents of \`pan-mcp.json\` to ZCode's MCP
|
|
40
40
|
configuration (Settings → MCP), or point ZCode's MCP config at this file. This
|
|
41
|
-
exposes the \`pan_*\` tools (
|
|
41
|
+
exposes the \`pan_*\` tools (roadmap analysis, phase lookup and previews, model resolution, phase report, next-action, merge gate) and the \`pan://\` resources (state, phases, progress, health, links, cost, findings).
|
|
42
42
|
|
|
43
43
|
2. **Import the subagents.** The \`agents/\` folder holds PAN agents in ZCode subagent
|
|
44
44
|
form. Prefer ZCode's **Import from Claude Code** / Settings → Subagents to register
|
package/scripts/build-hooks.js
CHANGED
package/scripts/build-plugin.js
CHANGED
|
@@ -77,7 +77,9 @@ function rewriteContent(content) {
|
|
|
77
77
|
.replace(/~\/\.claude\/agents\//g, `${CONTENT_PREFIX}agents/`)
|
|
78
78
|
.replace(/\.\/\.claude\/agents\//g, `${CONTENT_PREFIX}agents/`)
|
|
79
79
|
.replace(/~\/\.claude\//g, CONTENT_PREFIX)
|
|
80
|
-
.replace(/\.\/\.claude\//g, CONTENT_PREFIX)
|
|
80
|
+
.replace(/\.\/\.claude\//g, CONTENT_PREFIX)
|
|
81
|
+
// No `pan-tools` bin is on PATH under a plugin either: invoke it via node.
|
|
82
|
+
.replace(/\bpan-tools\b(?=\s+[a-z])/g, `node ${CONTENT_PREFIX}pan-wizard-core/bin/pan-tools.cjs`);
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
function copyTree(srcDir, destDir, transformMd) {
|
|
@@ -222,6 +222,13 @@ def get_version() -> str:
|
|
|
222
222
|
# SKILLS-FULL-TEXT.md generator
|
|
223
223
|
# ---------------------------------------------------------------------------
|
|
224
224
|
|
|
225
|
+
def wrapper_fence(content: str) -> str:
|
|
226
|
+
"""A backtick fence longer than any fence inside the content, so the skill's own
|
|
227
|
+
code blocks cannot close the block that wraps it."""
|
|
228
|
+
longest = max((len(m.group(1)) for m in re.finditer(r"^ {0,3}(`{3,})", content, re.M)), default=0)
|
|
229
|
+
return "`" * max(3, longest + 1)
|
|
230
|
+
|
|
231
|
+
|
|
225
232
|
def generate_full_text(shipped: list[Skill], dev: list[Skill], version: str) -> str:
|
|
226
233
|
"""Generate the complete full-text document."""
|
|
227
234
|
lines = []
|
|
@@ -247,9 +254,10 @@ def generate_full_text(shipped: list[Skill], dev: list[Skill], version: str) ->
|
|
|
247
254
|
w("")
|
|
248
255
|
w(f"### {skill.command} ({skill.line_count} lines)")
|
|
249
256
|
w("")
|
|
250
|
-
|
|
257
|
+
fence = wrapper_fence(skill.content)
|
|
258
|
+
w(f"{fence}markdown")
|
|
251
259
|
w(skill.content.rstrip())
|
|
252
|
-
w(
|
|
260
|
+
w(fence)
|
|
253
261
|
w("")
|
|
254
262
|
|
|
255
263
|
w("")
|
|
@@ -265,9 +273,10 @@ def generate_full_text(shipped: list[Skill], dev: list[Skill], version: str) ->
|
|
|
265
273
|
w("")
|
|
266
274
|
w(f"### {skill.command} ({skill.line_count} lines)")
|
|
267
275
|
w("")
|
|
268
|
-
|
|
276
|
+
fence = wrapper_fence(skill.content)
|
|
277
|
+
w(f"{fence}markdown")
|
|
269
278
|
w(skill.content.rstrip())
|
|
270
|
-
w(
|
|
279
|
+
w(fence)
|
|
271
280
|
w("")
|
|
272
281
|
|
|
273
282
|
return "\n".join(lines)
|
|
@@ -382,11 +391,11 @@ def generate_reference(shipped: list[Skill], dev: list[Skill], version: str) ->
|
|
|
382
391
|
w(s.description)
|
|
383
392
|
w("")
|
|
384
393
|
if s.argument_hint:
|
|
385
|
-
w(
|
|
394
|
+
w("```text")
|
|
386
395
|
w(f"{s.command} {s.argument_hint}")
|
|
387
396
|
w(f"```")
|
|
388
397
|
else:
|
|
389
|
-
w(
|
|
398
|
+
w("```text")
|
|
390
399
|
w(f"{s.command}")
|
|
391
400
|
w(f"```")
|
|
392
401
|
w("")
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Prompt-contract lint (program L2): the contract a shipped prompt has with the
|
|
5
|
+
* engine and the host, checked in code. Applied to commands/pan, agents and
|
|
6
|
+
* pan-wizard-core/workflows by tests/prompt-lint.test.cjs, with
|
|
7
|
+
* tests/fixtures/prompt-lint-allowlist.json (every entry names its reason, usually
|
|
8
|
+
* the plan item that fixes it).
|
|
9
|
+
*
|
|
10
|
+
* unknown-verb a `pan-tools <verb> [<sub>]` call naming a verb or a subcommand
|
|
11
|
+
* the engine does not have (tests/fixtures/surface.json)
|
|
12
|
+
* tool-not-granted an agent told to use a tool its frontmatter `tools:` does not grant
|
|
13
|
+
* pasted-source an @-include of engine source (`*.cjs` / `*.js`) into a prompt
|
|
14
|
+
* oversized-question an AskUserQuestion block offering more than four options (the
|
|
15
|
+
* host's limit: two to four)
|
|
16
|
+
* untyped-spawn a Task( spawn without `model=`, or a general-purpose spawn told
|
|
17
|
+
* to read a role file instead of naming the agent type
|
|
18
|
+
* dev-skill a shipped prompt sending the user to a dev-only skill of this
|
|
19
|
+
* repository (.claude/commands/*.md), which no install carries
|
|
20
|
+
*
|
|
21
|
+
* node scripts/prompt-lint.cjs # report the current tree, exit 1 on a finding
|
|
22
|
+
*/
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
const ROOT = path.join(__dirname, '..');
|
|
27
|
+
const TOOLS = ['Write', 'Edit', 'WebSearch', 'WebFetch', 'Task'];
|
|
28
|
+
|
|
29
|
+
function surface(root = ROOT) {
|
|
30
|
+
const s = JSON.parse(fs.readFileSync(path.join(root, 'tests', 'fixtures', 'surface.json'), 'utf8'));
|
|
31
|
+
const subs = new Map();
|
|
32
|
+
for (const entry of s.subcommands) {
|
|
33
|
+
const [verb, sub] = entry.split(' ');
|
|
34
|
+
if (!subs.has(verb)) subs.set(verb, new Set());
|
|
35
|
+
subs.get(verb).add(sub);
|
|
36
|
+
}
|
|
37
|
+
return { verbs: new Set(s.verbs), subs };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function devSkills(root = ROOT) {
|
|
41
|
+
try {
|
|
42
|
+
return fs.readdirSync(path.join(root, '.claude', 'commands')).filter((f) => f.endsWith('.md')).map((f) => f.replace(/\.md$/, ''));
|
|
43
|
+
} catch { return []; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const lineOf = (text, index) => text.slice(0, index).split('\n').length;
|
|
47
|
+
|
|
48
|
+
function frontmatterTools(text) {
|
|
49
|
+
const fm = text.match(/^---\n([\s\S]*?)\n---/);
|
|
50
|
+
if (!fm) return null;
|
|
51
|
+
const m = fm[1].match(/^tools:[ \t]*(.+)$/m);
|
|
52
|
+
return m ? new Set(m[1].split(',').map((t) => t.trim().replace(/\*$/, '')).filter(Boolean)) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Lint one file's text.
|
|
57
|
+
* @param {string} rel - repository-relative path (decides which rules apply)
|
|
58
|
+
* @param {string} raw - file content
|
|
59
|
+
* @param {{verbs: Set, subs: Map, dev: string[]}} ctx
|
|
60
|
+
* @returns {Array<{rule, file, line, text}>}
|
|
61
|
+
*/
|
|
62
|
+
function lintText(rel, raw, ctx) {
|
|
63
|
+
const text = raw.replace(/\r\n/g, '\n');
|
|
64
|
+
const out = [];
|
|
65
|
+
const hit = (rule, index, snippet) => out.push({ rule, file: rel, line: lineOf(text, index), text: String(snippet).trim().slice(0, 140) });
|
|
66
|
+
|
|
67
|
+
// unknown-verb
|
|
68
|
+
// An invocation, not prose: the script path (`pan-tools.cjs <verb>`) or a backticked `pan-tools <verb>`.
|
|
69
|
+
const callRe = /(?:pan-tools\.cjs"?|`pan-tools)[ \t]+([a-z][a-z0-9-]*)(?:[ \t]+([a-z][a-z0-9-]*))?/g;
|
|
70
|
+
let m;
|
|
71
|
+
while ((m = callRe.exec(text))) {
|
|
72
|
+
const [, verb, sub] = m;
|
|
73
|
+
if (!ctx.verbs.has(verb)) { hit('unknown-verb', m.index, m[0]); continue; }
|
|
74
|
+
const known = ctx.subs.get(verb);
|
|
75
|
+
if (known && sub && !known.has(sub) && !ctx.verbs.has(sub)) hit('unknown-verb', m.index, m[0]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// pasted-source
|
|
79
|
+
const incRe = /^@\S+\.(?:cjs|mjs|js)\s*$/gm;
|
|
80
|
+
while ((m = incRe.exec(text))) hit('pasted-source', m.index, m[0]);
|
|
81
|
+
|
|
82
|
+
// tool-not-granted (agents only)
|
|
83
|
+
if (rel.startsWith('agents/')) {
|
|
84
|
+
const granted = frontmatterTools(text);
|
|
85
|
+
if (granted) {
|
|
86
|
+
for (const tool of TOOLS) {
|
|
87
|
+
if (granted.has(tool)) continue;
|
|
88
|
+
const re = new RegExp(`\\b(?:use|using|with|via|call|run)\\s+(?:the\\s+)?\`?${tool}\`?(?:\\s+tool)?\\b|\\bthe\\s+\`?${tool}\`?\\s+tool\\b`, 'g');
|
|
89
|
+
let mm;
|
|
90
|
+
while ((mm = re.exec(text))) {
|
|
91
|
+
// "Never use Edit or Write tools" is the rule being kept, not broken.
|
|
92
|
+
const before = text.slice(Math.max(0, mm.index - 24), mm.index).toLowerCase();
|
|
93
|
+
if (/\b(never|not|don't|do not|no)\s*$/.test(before)) continue;
|
|
94
|
+
hit('tool-not-granted', mm.index, `${tool} (granted: ${[...granted].join(', ')}): ${mm[0]}`);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// oversized-question
|
|
102
|
+
const askRe = /AskUserQuestion/g;
|
|
103
|
+
while ((m = askRe.exec(text))) {
|
|
104
|
+
const window = text.slice(m.index, m.index + 6000);
|
|
105
|
+
const optRe = /options\s*:\s*\[/g;
|
|
106
|
+
let o;
|
|
107
|
+
while ((o = optRe.exec(window))) {
|
|
108
|
+
let depth = 0;
|
|
109
|
+
let end = -1;
|
|
110
|
+
for (let i = o.index + o[0].length - 1; i < window.length; i++) {
|
|
111
|
+
if (window[i] === '[') depth++;
|
|
112
|
+
else if (window[i] === ']') { depth--; if (depth === 0) { end = i; break; } }
|
|
113
|
+
}
|
|
114
|
+
if (end < 0) break;
|
|
115
|
+
const labels = (window.slice(o.index, end).match(/\blabel\s*:/g) || []).length;
|
|
116
|
+
if (labels > 4) hit('oversized-question', m.index + o.index, `${labels} options in one question`);
|
|
117
|
+
optRe.lastIndex = end;
|
|
118
|
+
}
|
|
119
|
+
askRe.lastIndex = m.index + 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// untyped-spawn (workflows and commands)
|
|
123
|
+
if (!rel.startsWith('agents/')) {
|
|
124
|
+
const lines = text.split('\n');
|
|
125
|
+
lines.forEach((l, i) => {
|
|
126
|
+
if (!/^\s*Task\(/.test(l)) return;
|
|
127
|
+
let j = i;
|
|
128
|
+
while (j < lines.length - 1 && !/^\s*\)\s*$/.test(lines[j]) && !/description="[^"]*"\s*\)\s*$/.test(lines[j])) j++;
|
|
129
|
+
const block = lines.slice(i, j + 1).join('\n');
|
|
130
|
+
const at = lines.slice(0, i).join('\n').length + 1;
|
|
131
|
+
if (!/\bmodel\s*=/.test(block)) hit('untyped-spawn', at, 'a Task( spawn without model=');
|
|
132
|
+
else if (/subagent_type="general-purpose"/.test(block) && /agents\/pan-[a-z-]+\.md for your role/.test(block)) hit('untyped-spawn', at, 'general-purpose spawn told to read a role file');
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// dev-skill
|
|
137
|
+
if (ctx.dev.length) {
|
|
138
|
+
// Every regex metacharacter escaped, backslash included (inline: CodeQL does not follow a helper).
|
|
139
|
+
const names = ctx.dev.map((d) => d.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&'));
|
|
140
|
+
const devRe = new RegExp(`(^|[\\s\`'"(])/(${names.join('|')})(?=[\\s\`'".,;:)]|$)`, 'gm');
|
|
141
|
+
while ((m = devRe.exec(text))) hit('dev-skill', m.index + m[1].length, m[0]);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The shipped prompt files, repository-relative. */
|
|
147
|
+
function shippedPrompts(root = ROOT) {
|
|
148
|
+
const list = [];
|
|
149
|
+
for (const dir of ['commands/pan', 'agents', 'pan-wizard-core/workflows']) {
|
|
150
|
+
for (const f of fs.readdirSync(path.join(root, dir)).filter((x) => x.endsWith('.md')).sort()) list.push(`${dir}/${f}`);
|
|
151
|
+
}
|
|
152
|
+
return list;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function lintTree(root = ROOT) {
|
|
156
|
+
const ctx = { ...surface(root), dev: devSkills(root) };
|
|
157
|
+
return shippedPrompts(root).flatMap((rel) => lintText(rel, fs.readFileSync(path.join(root, rel), 'utf8'), ctx));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (require.main === module) {
|
|
161
|
+
const findings = lintTree();
|
|
162
|
+
for (const f of findings) process.stdout.write(`${f.rule} ${f.file}:${f.line} — ${f.text}\n`);
|
|
163
|
+
process.stdout.write(`${findings.length} finding(s)\n`);
|
|
164
|
+
process.exitCode = findings.length ? 1 : 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { lintText, lintTree, shippedPrompts, surface, devSkills, frontmatterTools };
|