pan-wizard 3.4.1 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -8
- package/agents/pan-distiller.md +82 -0
- package/agents/pan-optimizer.md +242 -0
- package/bin/install.js +50 -1
- package/commands/pan/focus-auto.md +150 -3
- package/commands/pan/focus-exec.md +11 -0
- package/commands/pan/focus-scan.md +6 -0
- package/commands/pan/git.md +223 -0
- package/commands/pan/learn.md +61 -0
- package/commands/pan/milestone-done.md +9 -0
- package/commands/pan/optimize.md +86 -0
- package/hooks/dist/pan-trace-logger.js +197 -0
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/commands.cjs +1 -0
- package/pan-wizard-core/bin/lib/constants.cjs +5 -1
- package/pan-wizard-core/bin/lib/distill.cjs +510 -0
- package/pan-wizard-core/bin/lib/focus.cjs +8 -1
- package/pan-wizard-core/bin/lib/git.cjs +407 -0
- package/pan-wizard-core/bin/lib/optimize.cjs +653 -0
- package/pan-wizard-core/bin/pan-tools.cjs +78 -0
- package/pan-wizard-core/workflows/exec-phase.md +97 -0
- package/pan-wizard-core/workflows/learn.md +91 -0
- package/pan-wizard-core/workflows/optimize.md +139 -0
- package/pan-wizard-core/workflows/plan-phase.md +27 -0
- package/pan-wizard-core/workflows/quick.md +7 -0
- package/pan-wizard-core/workflows/verify-phase.md +16 -0
- package/scripts/build-hooks.js +2 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pan-trace-logger — SubagentStop hook (v3.5+).
|
|
3
|
+
//
|
|
4
|
+
// Fires alongside pan-cost-logger on every SubagentStop event. If a trace
|
|
5
|
+
// session is active (.planning/optimization/current-session exists), this
|
|
6
|
+
// hook appends a completion event to the trace. This is the automatic
|
|
7
|
+
// instrumentation layer of the circular optimization loop — no extra user
|
|
8
|
+
// action required.
|
|
9
|
+
//
|
|
10
|
+
// Events logged per subagent:
|
|
11
|
+
// - completion: agent finished, tokens used, exit status
|
|
12
|
+
// - redundancy: detected when the same agent type ran twice in this session
|
|
13
|
+
// with similar token counts (rough heuristic for repeated work)
|
|
14
|
+
//
|
|
15
|
+
// Errors are swallowed — this hook must never block the main agent loop.
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const PLANNING_DIR = '.planning';
|
|
21
|
+
const OPTIMIZE_DIR = 'optimization';
|
|
22
|
+
const TRACES_DIR = 'traces';
|
|
23
|
+
const CURRENT_SESSION_FILE = 'current-session';
|
|
24
|
+
const TRACE_EVENT_FILE = 'trace.jsonl';
|
|
25
|
+
|
|
26
|
+
function getOptimizeDir(cwd) {
|
|
27
|
+
return path.join(cwd, PLANNING_DIR, OPTIMIZE_DIR);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getTracesDir(cwd) {
|
|
31
|
+
return path.join(getOptimizeDir(cwd), TRACES_DIR);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getCurrentSessionId(cwd) {
|
|
35
|
+
try {
|
|
36
|
+
return fs.readFileSync(path.join(getOptimizeDir(cwd), CURRENT_SESSION_FILE), 'utf-8').trim() || null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Ensure a trace session exists. If none is active, create a day-scoped
|
|
44
|
+
* auto-session so tracing works across the whole flow without manual init.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} cwd
|
|
47
|
+
* @returns {string} The active session ID
|
|
48
|
+
*/
|
|
49
|
+
function ensureSessionId(cwd) {
|
|
50
|
+
const existing = getCurrentSessionId(cwd);
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
|
|
53
|
+
// Create a day-scoped auto session
|
|
54
|
+
const now = new Date();
|
|
55
|
+
const stamp = now.toISOString().replace(/[-:T]/g, '').slice(0, 8); // YYYYMMDD
|
|
56
|
+
const sessionId = `sess_auto_${stamp}`;
|
|
57
|
+
try {
|
|
58
|
+
const sessionDir = path.join(getTracesDir(cwd), sessionId);
|
|
59
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
60
|
+
const meta = {
|
|
61
|
+
session_id: sessionId,
|
|
62
|
+
started_at: now.toISOString(),
|
|
63
|
+
description: 'auto-session (day-scoped)',
|
|
64
|
+
auto: true,
|
|
65
|
+
event_count: 0,
|
|
66
|
+
};
|
|
67
|
+
fs.writeFileSync(path.join(sessionDir, 'session.json'), JSON.stringify(meta, null, 2) + '\n');
|
|
68
|
+
const optimizeDir = getOptimizeDir(cwd);
|
|
69
|
+
fs.mkdirSync(optimizeDir, { recursive: true });
|
|
70
|
+
fs.writeFileSync(path.join(optimizeDir, CURRENT_SESSION_FILE), sessionId + '\n');
|
|
71
|
+
return sessionId;
|
|
72
|
+
} catch {
|
|
73
|
+
return sessionId; // Return the ID even if write fails — best effort
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractNumber(obj, key) {
|
|
78
|
+
if (!obj || typeof obj !== 'object') return 0;
|
|
79
|
+
const v = obj[key];
|
|
80
|
+
return typeof v === 'number' ? v : 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build trace event(s) from a SubagentStop payload.
|
|
85
|
+
* Pure function — no side effects.
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} data - SubagentStop event payload
|
|
88
|
+
* @returns {Object[]} Array of trace event records
|
|
89
|
+
*/
|
|
90
|
+
function buildTraceEvents(data, sessionId) {
|
|
91
|
+
if (!data || typeof data !== 'object') return [];
|
|
92
|
+
if (data.hook_event_name && data.hook_event_name !== 'SubagentStop') return [];
|
|
93
|
+
|
|
94
|
+
const ts = new Date().toISOString();
|
|
95
|
+
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');
|
|
99
|
+
const totalTokens = inputTokens + outputTokens;
|
|
100
|
+
|
|
101
|
+
const events = [];
|
|
102
|
+
|
|
103
|
+
// Core completion event
|
|
104
|
+
events.push({
|
|
105
|
+
ts,
|
|
106
|
+
session: sessionId,
|
|
107
|
+
agent,
|
|
108
|
+
phase: data.phase || null,
|
|
109
|
+
type: 'decision',
|
|
110
|
+
category: 'agent_completion',
|
|
111
|
+
description: `${agent} completed`,
|
|
112
|
+
context: {
|
|
113
|
+
model: data.model || null,
|
|
114
|
+
input_tokens: inputTokens,
|
|
115
|
+
output_tokens: outputTokens,
|
|
116
|
+
cache_read_tokens: cacheRead,
|
|
117
|
+
total_tokens: totalTokens,
|
|
118
|
+
exit_code: data.exit_code || 0,
|
|
119
|
+
},
|
|
120
|
+
impact: 'trivial',
|
|
121
|
+
correction: null,
|
|
122
|
+
tokens_wasted: null,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Heuristic: if output tokens > 3000 and no cache hits, flag as potential redundancy
|
|
126
|
+
// (expensive agent run that wasn't cached — may be repeated research)
|
|
127
|
+
if (outputTokens > 3000 && cacheRead === 0) {
|
|
128
|
+
events.push({
|
|
129
|
+
ts,
|
|
130
|
+
session: sessionId,
|
|
131
|
+
agent,
|
|
132
|
+
phase: data.phase || null,
|
|
133
|
+
type: 'redundancy',
|
|
134
|
+
category: 'uncached_heavy_run',
|
|
135
|
+
description: `${agent} produced ${outputTokens} output tokens with zero cache hits — possible repeated research`,
|
|
136
|
+
context: { output_tokens: outputTokens, cache_read_tokens: 0 },
|
|
137
|
+
impact: 'minor',
|
|
138
|
+
correction: null,
|
|
139
|
+
tokens_wasted: outputTokens,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return events;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Append trace events to the active session.
|
|
148
|
+
* Returns true if written, false if no session or write failed.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} cwd
|
|
151
|
+
* @param {Object[]} events
|
|
152
|
+
* @param {string} sessionId
|
|
153
|
+
*/
|
|
154
|
+
function appendTraceEvents(cwd, events, sessionId) {
|
|
155
|
+
if (!events.length) return false;
|
|
156
|
+
try {
|
|
157
|
+
const sessionDir = path.join(getTracesDir(cwd), sessionId);
|
|
158
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
159
|
+
const lines = events.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
160
|
+
fs.appendFileSync(path.join(sessionDir, TRACE_EVENT_FILE), lines, 'utf-8');
|
|
161
|
+
return true;
|
|
162
|
+
} catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Stdin driver ────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
if (require.main === module) {
|
|
170
|
+
let input = '';
|
|
171
|
+
process.stdin.setEncoding('utf8');
|
|
172
|
+
process.stdin.on('data', chunk => (input += chunk));
|
|
173
|
+
process.stdin.on('end', () => {
|
|
174
|
+
try {
|
|
175
|
+
const data = JSON.parse(input);
|
|
176
|
+
const cwd = data.cwd || data.workspace?.current_dir || process.cwd();
|
|
177
|
+
// Always ensure a session exists — creates a day-scoped auto-session if needed
|
|
178
|
+
const sessionId = ensureSessionId(cwd);
|
|
179
|
+
const events = buildTraceEvents(data, sessionId);
|
|
180
|
+
appendTraceEvents(cwd, events, sessionId);
|
|
181
|
+
} catch {
|
|
182
|
+
// Silent fail
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
buildTraceEvents,
|
|
189
|
+
appendTraceEvents,
|
|
190
|
+
getCurrentSessionId,
|
|
191
|
+
ensureSessionId,
|
|
192
|
+
PLANNING_DIR,
|
|
193
|
+
OPTIMIZE_DIR,
|
|
194
|
+
TRACES_DIR,
|
|
195
|
+
CURRENT_SESSION_FILE,
|
|
196
|
+
TRACE_EVENT_FILE,
|
|
197
|
+
};
|
package/package.json
CHANGED
|
@@ -124,7 +124,7 @@ const FOCUS_DIR = 'focus';
|
|
|
124
124
|
const AUTO_RUN_FILE = 'auto-run.json';
|
|
125
125
|
|
|
126
126
|
/** Focus auto-runner categories */
|
|
127
|
-
const FOCUS_CATEGORIES = ['cleanup', 'tests', 'stability', 'features', 'docs', 'optimize', 'prompts'];
|
|
127
|
+
const FOCUS_CATEGORIES = ['cleanup', 'tests', 'stability', 'features', 'docs', 'optimize', 'prompts', 'security', 'distill'];
|
|
128
128
|
|
|
129
129
|
/** Category → priority index range (indices into PRIORITY_LEVELS) */
|
|
130
130
|
const CATEGORY_PRIORITY_RANGE = {
|
|
@@ -135,6 +135,8 @@ const CATEGORY_PRIORITY_RANGE = {
|
|
|
135
135
|
docs: { min: 5, max: 6 }, // P5-P6
|
|
136
136
|
optimize: { min: 1, max: 4 }, // P1-P4
|
|
137
137
|
prompts: { min: 0, max: 6 }, // P0-P6 (all priorities — prompt order is authoritative)
|
|
138
|
+
security: { min: 0, max: 2 }, // P0-P2 (critical/high/medium only — low/info skipped)
|
|
139
|
+
distill: { min: 1, max: 5 }, // P1-P5 (AI bloat: structural quality, not safety-critical)
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
/** Category → default mode + budget */
|
|
@@ -146,6 +148,8 @@ const CATEGORY_DEFAULTS = {
|
|
|
146
148
|
docs: { mode: 'balanced', budget: 30 },
|
|
147
149
|
optimize: { mode: 'balanced', budget: 50 },
|
|
148
150
|
prompts: { mode: 'balanced', budget: 100 },
|
|
151
|
+
security: { mode: 'bugfix', budget: 40 },
|
|
152
|
+
distill: { mode: 'balanced', budget: 50 },
|
|
149
153
|
};
|
|
150
154
|
|
|
151
155
|
/** Doc files to scan for staleness (focus sync) */
|