pan-wizard 3.5.2 → 3.7.10

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 (93) hide show
  1. package/README.md +8 -8
  2. package/agents/pan-executor.md +18 -0
  3. package/agents/pan-experiment-runner.md +126 -0
  4. package/agents/pan-phase-researcher.md +16 -0
  5. package/agents/pan-plan-checker.md +80 -0
  6. package/agents/pan-planner.md +19 -0
  7. package/agents/pan-reviewer.md +2 -0
  8. package/agents/pan-verifier.md +41 -0
  9. package/bin/install-lib.cjs +55 -0
  10. package/bin/install.js +71 -22
  11. package/commands/pan/debug.md +1 -1
  12. package/commands/pan/experiment.md +219 -0
  13. package/commands/pan/health.md +1 -1
  14. package/commands/pan/learn.md +15 -1
  15. package/commands/pan/optimize.md +13 -0
  16. package/commands/pan/patches.md +10 -1
  17. package/commands/pan/phase-tests.md +1 -4
  18. package/commands/pan/todo-add.md +1 -1
  19. package/commands/pan/todo-check.md +1 -1
  20. package/hooks/dist/pan-cost-logger.js +54 -4
  21. package/hooks/dist/pan-trace-logger.js +72 -3
  22. package/package.json +67 -66
  23. package/pan-wizard-core/bin/lib/commands.cjs +8 -0
  24. package/pan-wizard-core/bin/lib/config.cjs +13 -2
  25. package/pan-wizard-core/bin/lib/context-budget.cjs +73 -0
  26. package/pan-wizard-core/bin/lib/core.cjs +13 -0
  27. package/pan-wizard-core/bin/lib/doc-lint/frontmatter.js +270 -0
  28. package/pan-wizard-core/bin/lib/doc-lint/reporter.js +45 -0
  29. package/pan-wizard-core/bin/lib/doc-lint/schema.js +202 -0
  30. package/pan-wizard-core/bin/lib/doc-lint/validate.js +190 -0
  31. package/pan-wizard-core/bin/lib/doc-lint/walk.js +135 -0
  32. package/pan-wizard-core/bin/lib/doc-lint.cjs +287 -0
  33. package/pan-wizard-core/bin/lib/experiment.cjs +501 -0
  34. package/pan-wizard-core/bin/lib/learn-index.cjs +235 -0
  35. package/pan-wizard-core/bin/lib/learn-lint.cjs +292 -0
  36. package/pan-wizard-core/bin/lib/optimize.cjs +474 -1
  37. package/pan-wizard-core/bin/lib/runner.cjs +472 -0
  38. package/pan-wizard-core/bin/pan-tools.cjs +222 -2
  39. package/pan-wizard-core/learnings/README.md +70 -0
  40. package/pan-wizard-core/learnings/index.json +540 -0
  41. package/pan-wizard-core/learnings/internal/.gitkeep +2 -0
  42. package/pan-wizard-core/learnings/internal/experiment-runner.md +81 -0
  43. package/pan-wizard-core/learnings/internal/external-research.md +93 -0
  44. package/pan-wizard-core/learnings/internal/loop-design.md +33 -0
  45. package/pan-wizard-core/learnings/internal/pan-dev-bugs.md +181 -0
  46. package/pan-wizard-core/learnings/universal/.gitkeep +2 -0
  47. package/pan-wizard-core/learnings/universal/atomic-state.md +21 -0
  48. package/pan-wizard-core/learnings/universal/binary-io.md +21 -0
  49. package/pan-wizard-core/learnings/universal/comment-syntax.md +21 -0
  50. package/pan-wizard-core/learnings/universal/composition.md +33 -0
  51. package/pan-wizard-core/learnings/universal/concurrency.md +33 -0
  52. package/pan-wizard-core/learnings/universal/dag-scheduler.md +33 -0
  53. package/pan-wizard-core/learnings/universal/data-driven-design.md +21 -0
  54. package/pan-wizard-core/learnings/universal/design-process.md +21 -0
  55. package/pan-wizard-core/learnings/universal/empirical-spike.md +21 -0
  56. package/pan-wizard-core/learnings/universal/error-handling.md +23 -0
  57. package/pan-wizard-core/learnings/universal/error-paths.md +21 -0
  58. package/pan-wizard-core/learnings/universal/glob-semantics.md +21 -0
  59. package/pan-wizard-core/learnings/universal/idempotency.md +21 -0
  60. package/pan-wizard-core/learnings/universal/invariants.md +21 -0
  61. package/pan-wizard-core/learnings/universal/io-patterns.md +21 -0
  62. package/pan-wizard-core/learnings/universal/numeric-edge-cases.md +21 -0
  63. package/pan-wizard-core/learnings/universal/output-conventions.md +21 -0
  64. package/pan-wizard-core/learnings/universal/parser-design.md +21 -0
  65. package/pan-wizard-core/learnings/universal/phase-locking.md +21 -0
  66. package/pan-wizard-core/learnings/universal/pipe-friendly-cli.md +21 -0
  67. package/pan-wizard-core/learnings/universal/schema-design.md +21 -0
  68. package/pan-wizard-core/learnings/universal/secret-handling.md +21 -0
  69. package/pan-wizard-core/learnings/universal/streaming-io.md +21 -0
  70. package/pan-wizard-core/learnings/universal/test-patterns.md +57 -0
  71. package/pan-wizard-core/learnings/universal/test-strategy.md +33 -0
  72. package/pan-wizard-core/learnings/universal/unicode.md +21 -0
  73. package/pan-wizard-core/learnings/universal/vendor-pattern.md +21 -0
  74. package/pan-wizard-core/references/guardrails.md +58 -0
  75. package/pan-wizard-core/references/handoff-decisions.md +156 -0
  76. package/pan-wizard-core/references/schemas/pan-command.schema.yml +39 -0
  77. package/pan-wizard-core/references/verification-patterns.md +31 -0
  78. package/pan-wizard-core/templates/config.json +2 -1
  79. package/pan-wizard-core/templates/idea.md +52 -0
  80. package/pan-wizard-core/templates/summary-complex.md +14 -5
  81. package/pan-wizard-core/templates/summary-minimal.md +6 -0
  82. package/pan-wizard-core/templates/summary-standard.md +14 -3
  83. package/pan-wizard-core/workflows/discuss-phase.md +108 -1
  84. package/pan-wizard-core/workflows/exec-phase.md +37 -1
  85. package/pan-wizard-core/workflows/execute-plan.md +14 -0
  86. package/pan-wizard-core/workflows/health.md +23 -0
  87. package/pan-wizard-core/workflows/new-project.md +65 -81
  88. package/pan-wizard-core/workflows/plan-phase.md +58 -0
  89. package/pan-wizard-core/workflows/transition.md +102 -7
  90. package/pan-wizard-core/workflows/verify-phase.md +14 -0
  91. package/scripts/build-hooks.js +7 -1
  92. package/scripts/generate-skills-docs.py +10 -8
  93. package/scripts/release-check.js +184 -0
@@ -0,0 +1,219 @@
1
+ ---
2
+ name: experiment
3
+ group: Self-Improvement
4
+ description: Manage external experiments — scaffold, run, harvest, promote findings back to PAN
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Grep
11
+ - Glob
12
+ - Agent
13
+ ---
14
+
15
+ # /pan:experiment — Cross-Project Self-Improvement Loop
16
+
17
+ > **Self-protection:** This command **scaffolds external project folders OUTSIDE the PAN source repo** to drive autonomous AI coding sessions against fresh ideas, then harvests the resulting telemetry back into `pan-wizard-core/learnings/`. It is a **PAN-development tool**, not a feature for end-users of PAN to invoke on their own projects.
18
+
19
+ **Spec:** `docs/specs/self_improvement_loop_featureai.md`
20
+ **ADR:** ADR-0026 (pending W4)
21
+ **Status:** v3.7.0 W1+W2+W3 — scaffolding (`new`/`list`/`manifest`) + external runner (`run`/`status`/`stop`) + harvest (`harvest`/`prune`); W4 adds promote integration with `/pan:learn`.
22
+
23
+ ---
24
+
25
+ ## When to use this
26
+
27
+ - You have an idea for a small project. You want to know **how PAN drives that build** — what shortcuts the AI takes, where verification fails, which guardrails fire.
28
+ - You want the resulting telemetry to **flow back into PAN's shipped artifacts** so the next release is smarter.
29
+ - You're testing a behavioral change (new `references/` doc, new workflow rule) by running it against a real, isolated build.
30
+
31
+ ## When NOT to use this
32
+
33
+ - Building production user features. Use `/pan:new-project` and `/pan:exec-phase` directly.
34
+ - Validating a single-file change. The experiment loop is heavy — use `npm test` and `/pan:check`.
35
+ - Inside the PAN source repo. The command **refuses** to scaffold experiments inside `d:\PanWizard\` (or wherever the source is cloned). The experiment root defaults to `~/pan-experiments/`.
36
+
37
+ ---
38
+
39
+ ## Subcommands (W1 shipped)
40
+
41
+ ### `/pan:experiment new <slug> --idea <path>`
42
+
43
+ Scaffold a new experiment folder. Creates:
44
+
45
+ ```
46
+ <root>/<slug>/
47
+ ├── .planning/
48
+ │ ├── idea.md ← copied from --idea path
49
+ │ └── experiment.json ← manifest (slug, runtime, created_at, etc.)
50
+ └── .claude/ (or .codex/, .gemini/, .opencode/, .github/) ← PAN install for chosen runtime
51
+ ```
52
+
53
+ **Flags:**
54
+
55
+ | Flag | Default | Purpose |
56
+ |------|---------|---------|
57
+ | `--idea <path>` | required | Path to the idea.md doc; copied into the experiment |
58
+ | `--runtime <r>` | `claude` | Which AI coding runtime to install: claude / codex / gemini / opencode / copilot |
59
+ | `--root <path>` | `~/pan-experiments/` | Override the experiment root directory |
60
+ | `--budget <pts>` | `80` | Optional budget cap (saved to manifest, enforced by W2 runner) |
61
+ | `--skip-installer` | `false` | Don't run the PAN installer (dev-only) |
62
+
63
+ **Slug rules:** lowercase letters, digits, hyphens. Max 40 chars. No leading/trailing hyphen.
64
+
65
+ **Returns:** JSON with `experiment_id`, `path`, `runtime`, `idea_path`, `created_at`. On failure, returns `{ error: "..." }`.
66
+
67
+ ### `/pan:experiment list`
68
+
69
+ Enumerate all experiments under the root. Returns `{ experiments: [...], count }` sorted newest-first.
70
+
71
+ **Flags:**
72
+
73
+ | Flag | Default | Purpose |
74
+ |------|---------|---------|
75
+ | `--root <path>` | `~/pan-experiments/` | Override the root |
76
+ | `--raw` | `false` | Human-readable output instead of JSON |
77
+
78
+ ### `/pan:experiment manifest <slug>`
79
+
80
+ Read the manifest for a single experiment. Returns the JSON shape written by `new`.
81
+
82
+ **Flags:**
83
+
84
+ | Flag | Default | Purpose |
85
+ |------|---------|---------|
86
+ | `--root <path>` | `~/pan-experiments/` | Override the root |
87
+
88
+ ---
89
+
90
+ ## Subcommands (W2 shipped)
91
+
92
+ ### `/pan:experiment run <slug>`
93
+
94
+ Spawn the external AI runtime against the experiment folder. **Synchronous** — blocks until the external session exits, hits the timeout, or is stopped.
95
+
96
+ **Flags:**
97
+
98
+ | Flag | Default | Purpose |
99
+ |------|---------|---------|
100
+ | `--timeout <sec>` | `1800` (30 min) | Hard timeout in seconds; runner sends SIGTERM at deadline |
101
+ | `--prompt <text>` | `/pan:new-project --auto @.planning/idea.md` | Prompt passed to the external runtime |
102
+ | `--root <path>` | `~/pan-experiments/` | Override the experiment root |
103
+
104
+ **Returns:** `{ status: "done"\|"failed", stop_reason: "success"\|"error"\|"timeout"\|"manual", exit_code, elapsed_ms, started_at, ended_at }`. Run-state is also persisted to `<experiment>/.planning/run-state.json`.
105
+
106
+ **Runtime support:** claude / codex / gemini / opencode (via `RUNTIME_RUNNERS` adapter map in `runner.cjs`). GitHub Copilot CLI is **unsupported** for the `run` subcommand — no documented headless prompt mode. Copilot users can still scaffold and harvest manually.
107
+
108
+ ### `/pan:experiment status <slug>`
109
+
110
+ Read the current `run-state.json` snapshot. Returns the full state object (`status`, `stop_reason`, `exit_code`, `elapsed_ms`, `events`).
111
+
112
+ ### `/pan:experiment stop <slug>`
113
+
114
+ Gracefully halt a running experiment. Reads pid from `run-state.json`, sends SIGTERM, writes `status: failed, stop_reason: manual` to the run state. Returns the updated state.
115
+
116
+ If the experiment has already finished, returns the existing run state without error.
117
+
118
+ ## Subcommands (W3 shipped)
119
+
120
+ ### `/pan:experiment harvest <slug>`
121
+
122
+ Copy the experiment's telemetry into `<source-repo>/experiments/<slug>/` so it can be analyzed and promoted into shipped artifacts.
123
+
124
+ **What gets harvested** (skipped silently if absent):
125
+
126
+ - `.planning/idea.md` — the original idea doc
127
+ - `.planning/experiment.json` — scaffold manifest
128
+ - `.planning/state.md` — final project state
129
+ - `.planning/run-state.json` — runner result (status, exit_code, elapsed_ms)
130
+ - `.planning/agent-history.json` — every agent spawn during the build
131
+ - `.planning/optimization/` — full trace data (sessions, reports)
132
+ - `.planning/phases/` — phases the external session created
133
+
134
+ A `harvest.json` manifest is written at the destination capturing source path, timestamp, total bytes, and the list of harvested paths.
135
+
136
+ **Flags:**
137
+
138
+ | Flag | Default | Purpose |
139
+ |------|---------|---------|
140
+ | `--root <path>` | `~/pan-experiments/` | Override the experiment root |
141
+ | `--source-root <path>` | PAN source repo | Override harvest destination |
142
+ | `--force` | `false` | Overwrite an existing harvest at the destination |
143
+
144
+ **Returns:** `{ experiment_id, harvest_path, harvested_paths: [...], total_bytes, harvested_at, pan_version }`. On conflict without `--force`, returns `{ error }`.
145
+
146
+ ### `/pan:experiment prune <slug>`
147
+
148
+ Remove the experiment folder after harvest.
149
+
150
+ **Modes:**
151
+
152
+ - **Soft** (default): rename to `<root>/<slug>-archived-<ISO-timestamp>` so the data is retained but the slug is freed for reuse
153
+ - **Hard** (`--hard` flag): permanently delete
154
+
155
+ **Flags:**
156
+
157
+ | Flag | Default | Purpose |
158
+ |------|---------|---------|
159
+ | `--root <path>` | `~/pan-experiments/` | Override the experiment root |
160
+ | `--hard` | `false` | Permanent deletion (irreversible) |
161
+
162
+ **Returns:** `{ pruned: <slug>, mode: "soft"|"hard", archive_path? }`.
163
+
164
+ ## Subcommands (W4 — coming soon)
165
+
166
+ | Subcommand | Wave | Purpose |
167
+ |------------|------|---------|
168
+ | `archive <slug>` | W4 | Alias for `prune` (kept for clarity in scripts) |
169
+ | `delete <slug> --confirm` | W4 | Alias for `prune --hard` with confirmation prompt |
170
+
171
+ W4 also adds:
172
+ - `/pan:learn --experiment <slug>` — runs pan-optimizer over harvested data
173
+ - `pan-tools learn promote --pattern <id> --scope universal --topic <name>` — extracts a finding into `pan-wizard-core/learnings/{universal,internal}/<topic>.md`
174
+ - `pan-tools learn unpromote/list-promoted` — rollback and inventory
175
+
176
+ ---
177
+
178
+ ## CLI mapping (`pan-tools experiment <sub>`)
179
+
180
+ ```bash
181
+ pan-tools experiment new <slug> --idea <path> [--runtime r] [--root path] [--budget n]
182
+ pan-tools experiment list [--root path] [--raw]
183
+ pan-tools experiment manifest <slug> [--root path]
184
+ ```
185
+
186
+ Examples:
187
+
188
+ ```bash
189
+ # Scaffold a new experiment
190
+ echo "# Idea: Build a markdown linter CLI" > my-idea.md
191
+ # (fill in problem, success, scope, constraints — see template at pan-wizard-core/templates/idea.md)
192
+ pan-tools experiment new md-lint --idea my-idea.md --runtime claude --budget 60
193
+
194
+ # List all experiments
195
+ pan-tools experiment list
196
+
197
+ # Inspect one
198
+ pan-tools experiment manifest md-lint
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Safety guards
204
+
205
+ - **Never inside source repo.** `newExperiment` refuses to write to `d:\PanWizard\` (or wherever the PAN source is). Mirrors `bin/install.js` `PAN_SOURCE_ROOT` guard.
206
+ - **No clobber.** Refuses to scaffold over an existing experiment folder of the same slug.
207
+ - **Slug validation.** Lowercase + digits + hyphens, max 40 chars. Rejects uppercase, spaces, special characters.
208
+ - **Idea path validation.** Errors if the `--idea` file doesn't exist.
209
+
210
+ ## Related
211
+
212
+ - `pan-wizard-core/templates/idea.md` — idea doc template
213
+ - `pan-wizard-core/bin/lib/experiment.cjs` — implementation
214
+ - `pan-wizard-core/learnings/README.md` — where harvested findings go
215
+ - `docs/specs/self_improvement_loop_featureai.md` — full design
216
+
217
+ ## Runtime support
218
+
219
+ Works in all 5 runtimes (Claude / Codex / Gemini / OpenCode / Copilot). The W2 external runner (subprocess invocation of the external session) supports Claude / Codex / Gemini / OpenCode; GitHub Copilot CLI lacks a headless prompt mode and is opt-out for the `run` subcommand.
@@ -2,7 +2,7 @@
2
2
  name: pan:health
3
3
  group: System
4
4
  description: Diagnose planning directory health and optionally repair issues
5
- argument-hint: [--repair]
5
+ argument-hint: "[--repair]"
6
6
  allowed-tools:
7
7
  - Read
8
8
  - Bash
@@ -1,3 +1,15 @@
1
+ ---
2
+ name: pan:learn
3
+ group: Self-Improvement
4
+ description: Analyze trace sessions or harvested experiments via pan-optimizer; generate ranked optimization reports
5
+ allowed-tools:
6
+ - Read
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Task
11
+ ---
12
+
1
13
  # /pan:learn
2
14
 
3
15
  Analyze the most recent trace session and generate an optimization report.
@@ -6,11 +18,13 @@ Analyze the most recent trace session and generate an optimization report.
6
18
  ```
7
19
  /pan:learn
8
20
  /pan:learn --session <session-id>
21
+ /pan:learn --experiment <slug>
9
22
  /pan:learn --apply
10
23
  ```
11
24
 
12
25
  **Flags:**
13
26
  - `--session <id>` — analyze a specific session instead of the most recent
27
+ - `--experiment <slug>` *(v3.7.0+, W3)* — analyze a harvested experiment instead of the current project's traces. Reads from `<source-repo>/experiments/<slug>/.planning/optimization/` and writes the report to `<source-repo>/experiments/<slug>/learnings/report-<timestamp>.md`. Used by the self-improvement loop. Run `/pan:experiment harvest <slug>` first.
14
28
  - `--apply` — automatically apply safe optimizations after generating the report (equivalent to running `/pan:optimize apply` immediately after)
15
29
 
16
30
  **What it does:**
@@ -56,6 +70,6 @@ The optimization report in `.planning/optimization/reports/` contains:
56
70
  → Needs review: 2 prompt improvements, 1 workflow gap
57
71
  ```
58
72
 
59
- **See also:** `/pan:optimize`, `/pan:exec-phase`
73
+ **See also:** `/pan:optimize`, `/pan:exec-phase`, `/pan:experiment` (v3.7.0+ self-improvement loop)
60
74
 
61
75
  Follow the workflow at `.claude/workflows/learn.md` (or `pan-wizard-core/workflows/learn.md`).
@@ -1,3 +1,16 @@
1
+ ---
2
+ name: pan:optimize
3
+ group: Self-Improvement
4
+ description: Manage the circular optimization loop — apply recommendations, view stats, list reports, manage trace sessions
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Glob
11
+ - Grep
12
+ ---
13
+
1
14
  # /pan:optimize
2
15
 
3
16
  Manage the circular optimization loop: apply recommendations, view stats, list reports.
@@ -1,6 +1,15 @@
1
1
  ---
2
+ name: pan:patches
3
+ group: System
2
4
  description: Reapply local modifications after a PAN update
3
- allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Glob
11
+ - Grep
12
+ - AskUserQuestion
4
13
  ---
5
14
 
6
15
  <purpose>
@@ -12,10 +12,7 @@ allowed-tools:
12
12
  - Grep
13
13
  - Task
14
14
  - AskUserQuestion
15
- argument-instructions: |
16
- Parse the argument as a phase number (integer, decimal, or letter-suffix), plus optional free-text instructions.
17
- Example: /pan:phase-tests 12
18
- Example: /pan:phase-tests 12 focus on edge cases in the pricing module
15
+ argument-instructions: "Parse <phase> as a phase number (integer, decimal like 1.1, or letter-suffix like 1a) followed by optional free-text instructions. Examples: /pan:phase-tests 12 — /pan:phase-tests 12 focus on edge cases in the pricing module"
19
16
  ---
20
17
  <objective>
21
18
  Generate unit and E2E tests for a completed phase, using its summary.md, context.md, and verification.md as specifications.
@@ -2,7 +2,7 @@
2
2
  name: pan:todo-add
3
3
  group: System
4
4
  description: Capture idea or task as todo from current conversation context
5
- argument-hint: [optional description]
5
+ argument-hint: "[optional description]"
6
6
  allowed-tools:
7
7
  - Read
8
8
  - Write
@@ -2,7 +2,7 @@
2
2
  name: pan:todo-check
3
3
  group: System
4
4
  description: List pending todos and select one to work on
5
- argument-hint: [area filter]
5
+ argument-hint: "[area filter]"
6
6
  allowed-tools:
7
7
  - Read
8
8
  - Write
@@ -33,16 +33,32 @@ function buildCostRecord(data, cwd) {
33
33
  // Only log actual subagent stops; ignore other Stop variants.
34
34
  if (data.hook_event_name && data.hook_event_name !== 'SubagentStop') return null;
35
35
 
36
+ // P-1805 (v3.7.8): if data.usage is missing/empty (Claude Code headless mode
37
+ // doesn't include it in the SubagentStop payload), fall back to reading the
38
+ // transcript_path JSONL and summing usage across the subagent's messages.
39
+ // Same approach as pan-trace-logger.js for consistency.
40
+ let inputTokens = extractNumber(data.usage, 'input_tokens');
41
+ let outputTokens = extractNumber(data.usage, 'output_tokens');
42
+ let cacheRead = extractNumber(data.usage, 'cache_read_input_tokens');
43
+ let cacheWrite = extractNumber(data.usage, 'cache_creation_input_tokens');
44
+ if ((inputTokens + outputTokens + cacheRead + cacheWrite) === 0 && data.transcript_path) {
45
+ const fromTranscript = readUsageFromTranscript(data.transcript_path, data.session_id);
46
+ inputTokens = fromTranscript.input_tokens;
47
+ outputTokens = fromTranscript.output_tokens;
48
+ cacheRead = fromTranscript.cache_read_input_tokens;
49
+ cacheWrite = fromTranscript.cache_creation_input_tokens;
50
+ }
51
+
36
52
  const record = {
37
53
  ts: new Date().toISOString(),
38
54
  agent: data.agent_type || data.subagent_type || null,
39
55
  command: null,
40
56
  model: data.model || null,
41
57
  tier: null,
42
- input_tokens: extractNumber(data.usage, 'input_tokens') || 0,
43
- output_tokens: extractNumber(data.usage, 'output_tokens') || 0,
44
- cache_read_tokens: extractNumber(data.usage, 'cache_read_input_tokens') || 0,
45
- cache_write_tokens: extractNumber(data.usage, 'cache_creation_input_tokens') || 0,
58
+ input_tokens: inputTokens,
59
+ output_tokens: outputTokens,
60
+ cache_read_tokens: cacheRead,
61
+ cache_write_tokens: cacheWrite,
46
62
  cost_usd: null,
47
63
  phase: data.phase || null,
48
64
  session: data.session_id || null,
@@ -58,6 +74,40 @@ function extractNumber(obj, key) {
58
74
  return typeof v === 'number' ? v : 0;
59
75
  }
60
76
 
77
+ /**
78
+ * P-1805 (v3.7.8): read transcript JSONL and sum usage across all assistant
79
+ * messages belonging to the subagent's session. Mirrors the helper in
80
+ * pan-trace-logger.js. Returns zeros if transcript missing/unreadable.
81
+ */
82
+ function readUsageFromTranscript(transcriptPath, sessionId) {
83
+ const totals = {
84
+ input_tokens: 0,
85
+ output_tokens: 0,
86
+ cache_read_input_tokens: 0,
87
+ cache_creation_input_tokens: 0,
88
+ };
89
+ if (!transcriptPath || typeof transcriptPath !== 'string') return totals;
90
+ let raw;
91
+ try { raw = fs.readFileSync(transcriptPath, 'utf-8'); } catch { return totals; }
92
+ for (const line of raw.split('\n')) {
93
+ if (!line) continue;
94
+ let entry;
95
+ try { entry = JSON.parse(line); } catch { continue; }
96
+ if (sessionId && entry.session_id && entry.session_id !== sessionId) continue;
97
+ const usage = entry.usage
98
+ || entry.message?.usage
99
+ || entry.response?.usage
100
+ || (entry.type === 'assistant' && entry.message?.usage)
101
+ || null;
102
+ if (!usage || typeof usage !== 'object') continue;
103
+ totals.input_tokens += extractNumber(usage, 'input_tokens');
104
+ totals.output_tokens += extractNumber(usage, 'output_tokens');
105
+ totals.cache_read_input_tokens += extractNumber(usage, 'cache_read_input_tokens');
106
+ totals.cache_creation_input_tokens += extractNumber(usage, 'cache_creation_input_tokens');
107
+ }
108
+ return totals;
109
+ }
110
+
61
111
  /**
62
112
  * Append record to .planning/metrics/tokens.jsonl. Silently succeeds
63
113
  * even if the file or directory can't be written — hook must not block.
@@ -80,6 +80,65 @@ function extractNumber(obj, key) {
80
80
  return typeof v === 'number' ? v : 0;
81
81
  }
82
82
 
83
+ /**
84
+ * P-1805 (v3.7.8): extract usage totals by reading the SubagentStop transcript.
85
+ * The hook payload from Claude Code in headless mode does NOT include
86
+ * `data.usage` — only `transcript_path`. This function reads the transcript
87
+ * JSONL file and sums the `usage` fields across all assistant messages
88
+ * belonging to the just-completed subagent (its session_id is in `data.session_id`).
89
+ *
90
+ * Returns the SUM of input/output/cache tokens for the subagent's whole
91
+ * conversation. Agents that finish in one Claude API call will have one
92
+ * usage record; agents with multi-turn tool use will have several.
93
+ *
94
+ * Returns `{input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens}`
95
+ * with all zeros if the transcript is unreadable / missing — same fallback
96
+ * shape as the original behavior, so callers don't break.
97
+ *
98
+ * @param {string} transcriptPath - Absolute path to the transcript JSONL file
99
+ * @param {string} [sessionId] - Optional subagent session_id to filter on
100
+ * @returns {Object} usage totals object
101
+ */
102
+ function readUsageFromTranscript(transcriptPath, sessionId) {
103
+ const totals = {
104
+ input_tokens: 0,
105
+ output_tokens: 0,
106
+ cache_read_input_tokens: 0,
107
+ cache_creation_input_tokens: 0,
108
+ };
109
+ if (!transcriptPath || typeof transcriptPath !== 'string') return totals;
110
+ let raw;
111
+ try {
112
+ raw = fs.readFileSync(transcriptPath, 'utf-8');
113
+ } catch {
114
+ return totals;
115
+ }
116
+ for (const line of raw.split('\n')) {
117
+ if (!line) continue;
118
+ let entry;
119
+ try {
120
+ entry = JSON.parse(line);
121
+ } catch {
122
+ continue;
123
+ }
124
+ // Filter to entries from this subagent if a session_id is provided.
125
+ // The transcript may include parent + child traffic; session_id discriminates.
126
+ if (sessionId && entry.session_id && entry.session_id !== sessionId) continue;
127
+ // Usage typically lives on assistant message records.
128
+ const usage = entry.usage
129
+ || entry.message?.usage
130
+ || entry.response?.usage
131
+ || (entry.type === 'assistant' && entry.message?.usage)
132
+ || null;
133
+ if (!usage || typeof usage !== 'object') continue;
134
+ totals.input_tokens += extractNumber(usage, 'input_tokens');
135
+ totals.output_tokens += extractNumber(usage, 'output_tokens');
136
+ totals.cache_read_input_tokens += extractNumber(usage, 'cache_read_input_tokens');
137
+ totals.cache_creation_input_tokens += extractNumber(usage, 'cache_creation_input_tokens');
138
+ }
139
+ return totals;
140
+ }
141
+
83
142
  /**
84
143
  * Build trace event(s) from a SubagentStop payload.
85
144
  * Pure function — no side effects.
@@ -93,9 +152,19 @@ function buildTraceEvents(data, sessionId) {
93
152
 
94
153
  const ts = new Date().toISOString();
95
154
  const agent = data.agent_type || data.subagent_type || 'unknown';
96
- const inputTokens = extractNumber(data.usage, 'input_tokens');
97
- const outputTokens = extractNumber(data.usage, 'output_tokens');
98
- const cacheRead = extractNumber(data.usage, 'cache_read_input_tokens');
155
+
156
+ // P-1805: prefer usage from data.usage when present (interactive Claude Code path).
157
+ // Fall back to reading the transcript file (headless `claude -p` path — usage
158
+ // not in payload but discoverable via transcript_path).
159
+ let inputTokens = extractNumber(data.usage, 'input_tokens');
160
+ let outputTokens = extractNumber(data.usage, 'output_tokens');
161
+ let cacheRead = extractNumber(data.usage, 'cache_read_input_tokens');
162
+ if ((inputTokens + outputTokens + cacheRead) === 0 && data.transcript_path) {
163
+ const fromTranscript = readUsageFromTranscript(data.transcript_path, data.session_id);
164
+ inputTokens = fromTranscript.input_tokens;
165
+ outputTokens = fromTranscript.output_tokens;
166
+ cacheRead = fromTranscript.cache_read_input_tokens;
167
+ }
99
168
  const totalTokens = inputTokens + outputTokens;
100
169
 
101
170
  const events = [];
package/package.json CHANGED
@@ -1,66 +1,67 @@
1
- {
2
- "name": "pan-wizard",
3
- "version": "3.5.2",
4
- "description": "A lightweight workflow automation and context engineering system for Claude Code, OpenCode, Gemini CLI, Codex, and Copilot CLI.",
5
- "bin": {
6
- "pan-wizard": "bin/install.js"
7
- },
8
- "files": [
9
- "bin",
10
- "commands",
11
- "pan-wizard-core",
12
- "agents",
13
- "hooks/dist",
14
- "scripts",
15
- "assets"
16
- ],
17
- "keywords": [
18
- "claude",
19
- "claude-code",
20
- "ai",
21
- "workflow-automation",
22
- "context-engineering",
23
- "project-automation",
24
- "gemini",
25
- "gemini-cli",
26
- "codex",
27
- "codex-cli",
28
- "copilot",
29
- "copilot-cli",
30
- "github-copilot"
31
- ],
32
- "author": "PAN Wizard Contributors",
33
- "contributors": [
34
- {
35
- "name": "oharms",
36
- "url": "https://github.com/oharms"
37
- }
38
- ],
39
- "license": "MIT",
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/oharms/PanWizard.git"
43
- },
44
- "homepage": "https://github.com/oharms/PanWizard#readme",
45
- "bugs": {
46
- "url": "https://github.com/oharms/PanWizard/issues"
47
- },
48
- "engines": {
49
- "node": ">=16.7.0"
50
- },
51
- "devDependencies": {
52
- "@playwright/test": "^1.58.2",
53
- "@vscode/test-electron": "^2.5.2",
54
- "esbuild": "^0.24.0"
55
- },
56
- "scripts": {
57
- "build:hooks": "node scripts/build-hooks.js",
58
- "prepublishOnly": "npm run build:hooks",
59
- "test": "node --test tests/*.test.cjs",
60
- "test:scenarios": "node --test tests/scenarios/*.test.cjs",
61
- "test:all": "node --test tests/*.test.cjs tests/scenarios/*.test.cjs",
62
- "test:e2e": "node --test tests/scenarios/*.test.cjs",
63
- "test:vscode": "npx playwright test --config tests/e2e/playwright.config.mjs",
64
- "test:watch": "node --test --watch tests/*.test.cjs"
65
- }
66
- }
1
+ {
2
+ "name": "pan-wizard",
3
+ "version": "3.7.10",
4
+ "description": "A lightweight workflow automation and context engineering system for Claude Code, OpenCode, Gemini CLI, Codex, and Copilot CLI.",
5
+ "bin": {
6
+ "pan-wizard": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "commands",
11
+ "pan-wizard-core",
12
+ "agents",
13
+ "hooks/dist",
14
+ "scripts",
15
+ "assets"
16
+ ],
17
+ "keywords": [
18
+ "claude",
19
+ "claude-code",
20
+ "ai",
21
+ "workflow-automation",
22
+ "context-engineering",
23
+ "project-automation",
24
+ "gemini",
25
+ "gemini-cli",
26
+ "codex",
27
+ "codex-cli",
28
+ "copilot",
29
+ "copilot-cli",
30
+ "github-copilot"
31
+ ],
32
+ "author": "PAN Wizard Contributors",
33
+ "contributors": [
34
+ {
35
+ "name": "oharms",
36
+ "url": "https://github.com/oharms"
37
+ }
38
+ ],
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/oharms/PanWizard.git"
43
+ },
44
+ "homepage": "https://github.com/oharms/PanWizard#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/oharms/PanWizard/issues"
47
+ },
48
+ "engines": {
49
+ "node": ">=16.7.0"
50
+ },
51
+ "devDependencies": {
52
+ "@playwright/test": "^1.58.2",
53
+ "@vscode/test-electron": "^2.5.2",
54
+ "esbuild": "^0.24.0"
55
+ },
56
+ "scripts": {
57
+ "build:hooks": "node scripts/build-hooks.js",
58
+ "release:check": "node scripts/release-check.js",
59
+ "prepublishOnly": "node scripts/release-check.js",
60
+ "test": "node --test tests/*.test.cjs",
61
+ "test:scenarios": "node --test tests/scenarios/*.test.cjs",
62
+ "test:all": "node --test tests/*.test.cjs tests/scenarios/*.test.cjs",
63
+ "test:e2e": "node --test tests/scenarios/*.test.cjs",
64
+ "test:vscode": "npx playwright test --config tests/e2e/playwright.config.mjs",
65
+ "test:watch": "node --test --watch tests/*.test.cjs"
66
+ }
67
+ }
@@ -379,6 +379,11 @@ function runCommitSafetyChecks(cwd, config, force) {
379
379
  function cmdCommit(cwd, message, files, raw, amend, opts) {
380
380
  const commitType = opts && opts.type;
381
381
  const force = opts && opts.force;
382
+ // P-EXP-001 follow-up (v3.7.10): when failOnError is true, commit_failed
383
+ // exits non-zero so callers (especially autonomous loops) detect the silent-
384
+ // failure case where git refused (e.g. missing identity) and the loop would
385
+ // otherwise keep going thinking the artifact landed.
386
+ const failOnError = opts && opts.failOnError;
382
387
 
383
388
  if (!isGitRepo(cwd)) {
384
389
  output({ committed: false, hash: null, reason: 'not_a_git_repo', hint: 'Run git init to initialize a repository' }, raw, 'not a git repo');
@@ -425,6 +430,9 @@ function cmdCommit(cwd, message, files, raw, amend, opts) {
425
430
  output({ committed: false, hash: null, reason: 'nothing_to_commit' }, raw, 'nothing');
426
431
  return;
427
432
  }
433
+ if (failOnError) {
434
+ error('commit_failed: ' + (commitResult.stderr || 'unknown git error').trim());
435
+ }
428
436
  output({ committed: false, hash: null, reason: 'commit_failed', error: commitResult.stderr }, raw, 'failed');
429
437
  return;
430
438
  }