azclaude-copilot 0.3.3 → 0.3.4
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/bin/cli.js +1 -1
- package/package.json +1 -1
- package/templates/hooks/post-tool-use.js +34 -13
package/bin/cli.js
CHANGED
|
@@ -142,7 +142,7 @@ function buildHookEntries(scriptsDir) {
|
|
|
142
142
|
return {
|
|
143
143
|
UserPromptSubmit: [{ matcher: '', hooks: [{ type: 'command', command: `"${nodeExe}" "${userPromptScript}"` }] }],
|
|
144
144
|
Stop: [{ matcher: '', hooks: [{ type: 'command', command: `"${nodeExe}" "${stopScript}"` }] }],
|
|
145
|
-
PostToolUse: [{ matcher: 'Write|Edit', hooks: [{ type: 'command', command: `"${nodeExe}" "${postToolUseScript}"` }] }],
|
|
145
|
+
PostToolUse: [{ matcher: 'Write|Edit|Read|Bash|Grep', hooks: [{ type: 'command', command: `"${nodeExe}" "${postToolUseScript}"` }] }],
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
package/package.json
CHANGED
|
@@ -22,10 +22,12 @@ const HOOK_PROFILE = process.env.AZCLAUDE_HOOK_PROFILE || 'standard';
|
|
|
22
22
|
// Read tool input + response from stdin — Claude Code sends JSON and closes stdin
|
|
23
23
|
let filePath = '';
|
|
24
24
|
let changeSummary = '';
|
|
25
|
+
let toolName = '';
|
|
25
26
|
try {
|
|
26
27
|
const raw = fs.readFileSync(0, 'utf8'); // fd 0 = stdin, cross-platform
|
|
27
28
|
const data = JSON.parse(raw);
|
|
28
|
-
|
|
29
|
+
toolName = data.tool_name || '';
|
|
30
|
+
filePath = data.tool_input?.file_path || data.tool_input?.path || data.tool_input?.command || '';
|
|
29
31
|
// Extract change summary from old_string/new_string diff hint (Edit tool)
|
|
30
32
|
const oldStr = data.tool_input?.old_string || '';
|
|
31
33
|
const newStr = data.tool_input?.new_string || '';
|
|
@@ -40,13 +42,6 @@ try {
|
|
|
40
42
|
|
|
41
43
|
// Also accept env var fallback (older Claude Code versions)
|
|
42
44
|
if (!filePath) filePath = process.env.CLAUDE_FILE_PATH || '';
|
|
43
|
-
if (!filePath) process.exit(0);
|
|
44
|
-
|
|
45
|
-
// Guard: skip memory files (prevent write loop), skip non-project paths
|
|
46
|
-
const rel = path.relative(process.cwd(), path.resolve(filePath));
|
|
47
|
-
if (rel.startsWith('..')) process.exit(0); // outside project
|
|
48
|
-
if (/goals\.md$/.test(rel)) process.exit(0); // prevent loop
|
|
49
|
-
if (/node_modules[\\/]|\.git[\\/]/.test(rel)) process.exit(0); // noise
|
|
50
45
|
|
|
51
46
|
const cfg = process.env.AZCLAUDE_CFG || '.claude';
|
|
52
47
|
// Guard: cfg must resolve inside the project root
|
|
@@ -54,10 +49,24 @@ if (path.resolve(cfg).indexOf(process.cwd()) !== 0) process.exit(0);
|
|
|
54
49
|
const goalsPath = path.join(cfg, 'memory', 'goals.md');
|
|
55
50
|
if (!fs.existsSync(goalsPath)) process.exit(0); // not an AZCLAUDE project
|
|
56
51
|
|
|
52
|
+
// For non-file tools (Bash, Grep without file_path), still capture observations but skip goals tracking
|
|
53
|
+
const isFileTool = toolName === 'Write' || toolName === 'Edit' || (!toolName && filePath);
|
|
54
|
+
const rel = filePath ? path.relative(process.cwd(), path.resolve(filePath)) : toolName || 'unknown';
|
|
55
|
+
|
|
56
|
+
if (isFileTool) {
|
|
57
|
+
if (!filePath) process.exit(0);
|
|
58
|
+
if (rel.startsWith('..')) process.exit(0); // outside project
|
|
59
|
+
if (/goals\.md$/.test(rel)) process.exit(0); // prevent loop
|
|
60
|
+
if (/node_modules[\\/]|\.git[\\/]/.test(rel)) process.exit(0); // noise
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
// Timestamp HH:MM
|
|
58
64
|
const now = new Date();
|
|
59
65
|
const ts = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
60
66
|
|
|
67
|
+
// ── Goals.md tracking (Write/Edit only — file modifications) ────────────────
|
|
68
|
+
if (isFileTool) {
|
|
69
|
+
|
|
61
70
|
// Git diff stat: "+N/-M" — cached for 5s to avoid repeated git calls on consecutive edits
|
|
62
71
|
let diffStat = '';
|
|
63
72
|
const diffCachePath = path.join(os.tmpdir(), `.azclaude-diff-${process.ppid || process.pid}`);
|
|
@@ -114,20 +123,32 @@ if (!content.includes(HEADING)) {
|
|
|
114
123
|
|
|
115
124
|
try { fs.writeFileSync(goalsPath, content); } catch (_) {}
|
|
116
125
|
|
|
126
|
+
} // end isFileTool goals tracking
|
|
127
|
+
|
|
117
128
|
// ── Reflex observation capture (standard/strict only) ───────────────────────
|
|
118
129
|
// Append tool-use observation to observations.jsonl for pattern detection.
|
|
119
|
-
//
|
|
130
|
+
// Tracks actual tool name + tool sequences (last 3 tools) for pattern detection.
|
|
120
131
|
if (HOOK_PROFILE !== 'minimal') {
|
|
121
132
|
const reflexDir = path.join(cfg, 'memory', 'reflexes');
|
|
122
133
|
try {
|
|
123
134
|
fs.mkdirSync(reflexDir, { recursive: true });
|
|
124
135
|
const obsPath = path.join(reflexDir, 'observations.jsonl');
|
|
125
136
|
const obsTs = now.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
126
|
-
const tool = 'Edit';
|
|
137
|
+
const tool = toolName || 'Edit';
|
|
127
138
|
// Scrub secrets: strip API keys, tokens, passwords from file paths
|
|
128
139
|
const safeRel = rel.replace(/\.(env|key|pem|secret|credential)/gi, '.[REDACTED]');
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
|
|
141
|
+
// Track tool sequence: last 3 tools for pattern detection (Read→Edit→Bash)
|
|
142
|
+
const seqPath = path.join(os.tmpdir(), `.azclaude-seq-${process.ppid || process.pid}`);
|
|
143
|
+
let seq = [];
|
|
144
|
+
try { seq = JSON.parse(fs.readFileSync(seqPath, 'utf8')); } catch (_) {}
|
|
145
|
+
seq.push(tool);
|
|
146
|
+
if (seq.length > 3) seq = seq.slice(-3);
|
|
147
|
+
try { fs.writeFileSync(seqPath, JSON.stringify(seq)); } catch (_) {}
|
|
148
|
+
|
|
149
|
+
const obs = JSON.stringify({
|
|
150
|
+
ts: obsTs, tool, file: safeRel, session: process.ppid || process.pid,
|
|
151
|
+
event: 'complete', seq: seq.join('→')
|
|
131
152
|
});
|
|
132
153
|
fs.appendFileSync(obsPath, obs + '\n');
|
|
133
154
|
// Auto-truncate: keep last 2000 lines max (prevent unbounded growth)
|
|
@@ -150,7 +171,7 @@ if (HOOK_PROFILE !== 'minimal') {
|
|
|
150
171
|
const costsPath = path.join(costsDir, 'costs.jsonl');
|
|
151
172
|
const costEntry = JSON.stringify({
|
|
152
173
|
ts: now.toISOString().replace(/\.\d{3}Z$/, 'Z'),
|
|
153
|
-
tool: 'Edit',
|
|
174
|
+
tool: toolName || 'Edit',
|
|
154
175
|
file: rel,
|
|
155
176
|
session: process.ppid || process.pid
|
|
156
177
|
});
|