pan-wizard 2.9.1 → 3.5.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 +31 -9
- package/agents/pan-conductor.md +189 -0
- package/agents/pan-counterfactual.md +112 -0
- package/agents/pan-debugger.md +15 -1
- package/agents/pan-distiller.md +82 -0
- package/agents/pan-document_code.md +21 -0
- package/agents/pan-executor.md +16 -0
- package/agents/pan-hardener.md +113 -0
- package/agents/pan-integration-checker.md +2 -0
- package/agents/pan-knowledge.md +81 -0
- package/agents/pan-meta-reviewer.md +91 -0
- package/agents/pan-optimizer.md +242 -0
- package/agents/pan-plan-checker.md +2 -0
- package/agents/pan-previewer.md +98 -0
- package/agents/pan-project-researcher.md +4 -4
- package/agents/pan-reviewer.md +2 -0
- package/agents/pan-verifier.md +2 -0
- package/bin/install-lib.cjs +197 -0
- package/bin/install.js +2048 -1959
- package/commands/pan/cost.md +132 -0
- package/commands/pan/exec-phase.md +15 -0
- package/commands/pan/focus-auto.md +168 -3
- package/commands/pan/focus-exec.md +21 -1
- package/commands/pan/focus-scan.md +6 -0
- package/commands/pan/git.md +223 -0
- package/commands/pan/knowledge.md +129 -0
- package/commands/pan/learn.md +61 -0
- package/commands/pan/map-codebase.md +15 -0
- package/commands/pan/mcp-bridge.md +145 -0
- package/commands/pan/milestone-done.md +9 -0
- package/commands/pan/optimize.md +86 -0
- package/commands/pan/plan-phase.md +11 -0
- package/commands/pan/preview.md +114 -0
- package/commands/pan/profile.md +37 -0
- package/commands/pan/review-deep.md +128 -0
- package/commands/pan/verify-phase.md +11 -0
- package/commands/pan/what-if.md +146 -0
- package/hooks/dist/pan-cost-logger.js +102 -0
- package/hooks/dist/pan-statusline.js +154 -108
- package/hooks/dist/pan-trace-logger.js +197 -0
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/bridge.cjs +269 -0
- package/pan-wizard-core/bin/lib/bus.cjs +251 -0
- package/pan-wizard-core/bin/lib/codebase.cjs +118 -0
- package/pan-wizard-core/bin/lib/commands.cjs +1 -0
- package/pan-wizard-core/bin/lib/constants.cjs +44 -1
- package/pan-wizard-core/bin/lib/context-budget.cjs +27 -0
- package/pan-wizard-core/bin/lib/core.cjs +91 -6
- package/pan-wizard-core/bin/lib/cost.cjs +359 -0
- package/pan-wizard-core/bin/lib/distill.cjs +510 -0
- package/pan-wizard-core/bin/lib/focus.cjs +108 -3
- package/pan-wizard-core/bin/lib/git.cjs +407 -0
- package/pan-wizard-core/bin/lib/init.cjs +5 -5
- package/pan-wizard-core/bin/lib/knowledge.cjs +331 -0
- package/pan-wizard-core/bin/lib/memory.cjs +252 -0
- package/pan-wizard-core/bin/lib/optimize.cjs +653 -0
- package/pan-wizard-core/bin/lib/phase.cjs +40 -13
- package/pan-wizard-core/bin/lib/preview.cjs +480 -0
- package/pan-wizard-core/bin/lib/review-deep.cjs +280 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +4 -4
- package/pan-wizard-core/bin/lib/state.cjs +2 -2
- package/pan-wizard-core/bin/lib/verify.cjs +34 -1
- package/pan-wizard-core/bin/lib/whatif.cjs +289 -0
- package/pan-wizard-core/bin/pan-tools.cjs +317 -4
- package/pan-wizard-core/templates/playbook.md +53 -0
- package/pan-wizard-core/templates/preview-report.md +93 -0
- package/pan-wizard-core/templates/roadmap.md +24 -24
- package/pan-wizard-core/templates/state.md +12 -9
- 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 +28 -1
- package/pan-wizard-core/workflows/quick.md +7 -0
- package/pan-wizard-core/workflows/verify-phase.md +16 -0
- package/scripts/build-hooks.js +3 -1
|
@@ -1,108 +1,154 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Claude Code Statusline - PAN Edition
|
|
3
|
-
// Shows: model | current task | directory | context
|
|
4
|
-
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code Statusline - PAN Edition
|
|
3
|
+
// Shows: model | current task | directory | context | cache | thinking
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build the statusline output string from the JSON payload Claude Code pipes
|
|
11
|
+
* to stdin. Pure function — no stdin, no stdout, no process exit. Safe to
|
|
12
|
+
* call from tests.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} data - Parsed stdin JSON from Claude Code.
|
|
15
|
+
* @param {Object} [deps] - Optional dep injection for testing.
|
|
16
|
+
* {fs, path, homeDir, tmpDir} — defaults to real modules + OS paths.
|
|
17
|
+
* @returns {string} The statusline content.
|
|
18
|
+
*/
|
|
19
|
+
function buildStatuslineOutput(data, deps) {
|
|
20
|
+
const d = deps || {};
|
|
21
|
+
const fsMod = d.fs || fs;
|
|
22
|
+
const pathMod = d.path || path;
|
|
23
|
+
const homeDir = d.homeDir || os.homedir();
|
|
24
|
+
const tmpDir = d.tmpDir || os.tmpdir();
|
|
25
|
+
|
|
26
|
+
if (!data || typeof data !== 'object') return '';
|
|
27
|
+
|
|
28
|
+
const model = data.model?.display_name || 'Claude';
|
|
29
|
+
const dir = data.workspace?.current_dir || process.cwd();
|
|
30
|
+
const session = data.session_id || '';
|
|
31
|
+
const remaining = data.context_window?.remaining_percentage;
|
|
32
|
+
|
|
33
|
+
// Context window bar — shows USED percentage scaled so 80% real = 100% shown.
|
|
34
|
+
let ctx = '';
|
|
35
|
+
if (remaining != null) {
|
|
36
|
+
const rem = Math.round(remaining);
|
|
37
|
+
const rawUsed = Math.max(0, Math.min(100, 100 - rem));
|
|
38
|
+
const used = Math.min(100, Math.round((rawUsed / 80) * 100));
|
|
39
|
+
|
|
40
|
+
if (session && d.skipBridge !== true) {
|
|
41
|
+
try {
|
|
42
|
+
const bridgePath = pathMod.join(tmpDir, `claude-ctx-${session}.json`);
|
|
43
|
+
fsMod.writeFileSync(bridgePath, JSON.stringify({
|
|
44
|
+
session_id: session,
|
|
45
|
+
remaining_percentage: remaining,
|
|
46
|
+
used_pct: used,
|
|
47
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
48
|
+
}));
|
|
49
|
+
} catch { /* bridge is best-effort */ }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const filled = Math.floor(used / 10);
|
|
53
|
+
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
|
54
|
+
|
|
55
|
+
if (used < 63) {
|
|
56
|
+
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
|
57
|
+
} else if (used < 81) {
|
|
58
|
+
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
|
59
|
+
} else if (used < 95) {
|
|
60
|
+
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
|
61
|
+
} else {
|
|
62
|
+
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// E-8: Opus 4.7 indicators. Read from stdin data first (if present),
|
|
67
|
+
// else merge from optional bridge file `claude-pan-<session>.json` that
|
|
68
|
+
// an agent or external process can write.
|
|
69
|
+
let panExtras = null;
|
|
70
|
+
if (session) {
|
|
71
|
+
try {
|
|
72
|
+
const extrasPath = pathMod.join(tmpDir, `claude-pan-${session}.json`);
|
|
73
|
+
const extrasRaw = fsMod.readFileSync(extrasPath, 'utf8');
|
|
74
|
+
panExtras = JSON.parse(extrasRaw);
|
|
75
|
+
} catch { /* no extras is fine */ }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const thinkingActive = (data.thinking && data.thinking.active === true)
|
|
79
|
+
|| (panExtras && panExtras.thinking_active === true);
|
|
80
|
+
const cacheHitRate = (data.cache && typeof data.cache.hit_rate_pct === 'number')
|
|
81
|
+
? data.cache.hit_rate_pct
|
|
82
|
+
: (panExtras && typeof panExtras.cache_hit_rate_pct === 'number'
|
|
83
|
+
? panExtras.cache_hit_rate_pct
|
|
84
|
+
: null);
|
|
85
|
+
|
|
86
|
+
let thinkingBadge = '';
|
|
87
|
+
if (thinkingActive) thinkingBadge = ' \x1b[35m🧠\x1b[0m';
|
|
88
|
+
|
|
89
|
+
let cacheBadge = '';
|
|
90
|
+
if (cacheHitRate != null) {
|
|
91
|
+
const pct = Math.max(0, Math.min(100, Math.round(cacheHitRate)));
|
|
92
|
+
// Color: green ≥70%, yellow 30-70%, dim <30% (warmup).
|
|
93
|
+
const color = pct >= 70 ? '\x1b[32m' : pct >= 30 ? '\x1b[33m' : '\x1b[2m';
|
|
94
|
+
cacheBadge = ` ${color}⚡${pct}%\x1b[0m`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Current task from todos
|
|
98
|
+
let task = '';
|
|
99
|
+
const todosDir = pathMod.join(homeDir, '.claude', 'todos');
|
|
100
|
+
let todosExists = false;
|
|
101
|
+
try { todosExists = fsMod.statSync(todosDir).isDirectory(); } catch { /* missing */ }
|
|
102
|
+
if (session && todosExists) {
|
|
103
|
+
try {
|
|
104
|
+
const files = fsMod.readdirSync(todosDir)
|
|
105
|
+
.filter(f => f.startsWith(session) && f.includes('-agent-') && f.endsWith('.json'))
|
|
106
|
+
.map(f => {
|
|
107
|
+
try { return { name: f, mtime: fsMod.statSync(pathMod.join(todosDir, f)).mtime }; }
|
|
108
|
+
catch { return null; }
|
|
109
|
+
})
|
|
110
|
+
.filter(Boolean)
|
|
111
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
112
|
+
|
|
113
|
+
if (files.length > 0) {
|
|
114
|
+
try {
|
|
115
|
+
const todos = JSON.parse(fsMod.readFileSync(pathMod.join(todosDir, files[0].name), 'utf8'));
|
|
116
|
+
const inProgress = todos.find(t => t.status === 'in_progress');
|
|
117
|
+
if (inProgress) task = inProgress.activeForm || '';
|
|
118
|
+
} catch { /* malformed todos file — skip */ }
|
|
119
|
+
}
|
|
120
|
+
} catch { /* fs errors — silent */ }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// PAN update available?
|
|
124
|
+
let panUpdate = '';
|
|
125
|
+
const cacheFile = pathMod.join(homeDir, '.claude', 'cache', 'pan-update-check.json');
|
|
126
|
+
try {
|
|
127
|
+
const cache = JSON.parse(fsMod.readFileSync(cacheFile, 'utf8'));
|
|
128
|
+
if (cache.update_available) panUpdate = '\x1b[33m⬆ /pan:update\x1b[0m │ ';
|
|
129
|
+
} catch { /* no update cache — silent */ }
|
|
130
|
+
|
|
131
|
+
const dirname = pathMod.basename(dir);
|
|
132
|
+
const head = `${panUpdate}\x1b[2m${model}\x1b[0m`;
|
|
133
|
+
const taskSegment = task ? ` │ \x1b[1m${task}\x1b[0m` : '';
|
|
134
|
+
const dirSegment = ` │ \x1b[2m${dirname}\x1b[0m`;
|
|
135
|
+
return `${head}${taskSegment}${dirSegment}${ctx}${cacheBadge}${thinkingBadge}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── Stdin driver ───────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
if (require.main === module) {
|
|
141
|
+
let input = '';
|
|
142
|
+
process.stdin.setEncoding('utf8');
|
|
143
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
144
|
+
process.stdin.on('end', () => {
|
|
145
|
+
try {
|
|
146
|
+
const data = JSON.parse(input);
|
|
147
|
+
process.stdout.write(buildStatuslineOutput(data));
|
|
148
|
+
} catch {
|
|
149
|
+
// Silent fail — don't break statusline on parse errors.
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { buildStatuslineOutput };
|
|
@@ -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