claudify-agent 1.0.1 → 1.0.2
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/.claude/QUICK-REFERENCE.md +213 -213
- package/.claude/cost-dashboard.md +162 -162
- package/.claude/docs/COST-OPTIMIZATION.md +402 -403
- package/.claude/docs/MCP-INTEGRATION-GUIDE.md +348 -348
- package/.claude/docs/PLAN-MODE-OPTIMIZATION.md +835 -836
- package/.claude/hooks/auto-learning-review.ts +200 -195
- package/.claude/hooks/auto-lint-fix.ts +60 -104
- package/.claude/hooks/auto-security-review.ts +314 -354
- package/.claude/hooks/auto-validate.ts +249 -348
- package/.claude/hooks/lib/claude-hook.ts +168 -0
- package/.claude/hooks/plan-quality-tracker.ts +598 -719
- package/.claude/rules/anti-patterns.md +22 -22
- package/.claude/rules/context-tier-1.md +41 -41
- package/.claude/rules/context-tier-2.md +285 -285
- package/.claude/rules/general.md +25 -25
- package/.claude/rules/mcp-auto-use.md +46 -46
- package/.claude/rules/parallel-exploration-auto.md +30 -31
- package/.claude/rules/plan-mode-execution.md +289 -289
- package/.claude/rules/plan-mode-research.md +34 -34
- package/.claude/rules/plan-mode-structure.md +166 -166
- package/.claude/rules/plan-mode-validation.md +129 -130
- package/.claude/rules/security-critical.md +19 -19
- package/.claude/rules/tool-auto-selection.md +45 -45
- package/.claude/rules/tool-strategy.md +22 -22
- package/.claude/scripts/security-scan.js +1 -1
- package/.claude/security-dashboard.md +142 -142
- package/.claude/settings.json +50 -0
- package/.claude/skills/debug-optimizer/SKILL.md +420 -399
- package/.claude/skills/learning-loop/SKILL.md +313 -308
- package/.claude/skills/mcp-mastery/SKILL.md +218 -214
- package/.claude/skills/nextjs-guide/SKILL.md +687 -687
- package/.claude/skills/parallel-exploration/SKILL.md +197 -193
- package/.claude/skills/plan-mode-mastery/SKILL.md +268 -263
- package/.claude/skills/python-guide/SKILL.md +822 -822
- package/.claude/skills/react-guide/SKILL.md +762 -762
- package/.claude/skills/{security.md → security-guide/SKILL.md} +10 -0
- package/.claude/skills/solidity-guide/SKILL.md +5 -0
- package/.claude/skills/tool-selection/SKILL.md +433 -428
- package/.claude/skills/vite-guide/SKILL.md +445 -445
- package/CLAUDE.md +1 -0
- package/README.md +74 -35
- package/README.vi.md +106 -0
- package/bin/cli.js +13 -4
- package/package.json +9 -1
- package/.claude/skills/debug-optimizer.md +0 -12
- package/.claude/skills/nextjs.md +0 -13
- package/.claude/skills/react.md +0 -12
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// .claude/hooks/lib/claude-hook.ts
|
|
2
|
+
// Shared Claude Code hook I/O: stdin payload, transcript parsing, hook output
|
|
3
|
+
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
export interface HookInput {
|
|
8
|
+
session_id: string;
|
|
9
|
+
transcript_path: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
hook_event_name: string;
|
|
12
|
+
stop_hook_active?: boolean;
|
|
13
|
+
tool_name?: string;
|
|
14
|
+
tool_input?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ToolUse {
|
|
18
|
+
name: string;
|
|
19
|
+
input: Record<string, unknown>;
|
|
20
|
+
isError: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ContentBlock {
|
|
24
|
+
type: string;
|
|
25
|
+
id?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
input?: Record<string, unknown>;
|
|
28
|
+
tool_use_id?: string;
|
|
29
|
+
is_error?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface TranscriptEntry {
|
|
33
|
+
type?: string;
|
|
34
|
+
uuid?: string;
|
|
35
|
+
isMeta?: boolean;
|
|
36
|
+
isSidechain?: boolean;
|
|
37
|
+
message?: { role?: string; content?: string | ContentBlock[] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Turn {
|
|
41
|
+
id: string;
|
|
42
|
+
entries: TranscriptEntry[];
|
|
43
|
+
toolUses: ToolUse[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Shape of the error thrown by execSync/execFileSync on a non-zero exit
|
|
47
|
+
export interface ExecError {
|
|
48
|
+
status?: number | null;
|
|
49
|
+
stdout?: string | Buffer;
|
|
50
|
+
stderr?: string | Buffer;
|
|
51
|
+
message: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const EDIT_TOOLS = ["Edit", "Write", "MultiEdit", "NotebookEdit"];
|
|
55
|
+
export const SUBAGENT_TOOLS = ["Agent", "Task"];
|
|
56
|
+
|
|
57
|
+
export async function readHookInput(): Promise<HookInput> {
|
|
58
|
+
const stdin = await new Promise<string>((resolve) => {
|
|
59
|
+
let data = "";
|
|
60
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
61
|
+
process.stdin.on("end", () => resolve(data));
|
|
62
|
+
});
|
|
63
|
+
return JSON.parse(stdin);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Project-local state lives next to the hooks, in <project>/.claude
|
|
67
|
+
export function stateDir(input: HookInput): string {
|
|
68
|
+
return join(process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd(), ".claude");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isUserPrompt(entry: TranscriptEntry): boolean {
|
|
72
|
+
if (entry.type !== "user" || entry.isMeta || entry.isSidechain) return false;
|
|
73
|
+
const content = entry.message?.content;
|
|
74
|
+
if (typeof content === "string") return true;
|
|
75
|
+
return Array.isArray(content) && content.some((b) => b.type === "text");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The current turn: everything after the last prompt the user typed
|
|
79
|
+
export function loadCurrentTurn(transcriptPath: string): Turn | null {
|
|
80
|
+
if (!transcriptPath || !existsSync(transcriptPath)) return null;
|
|
81
|
+
|
|
82
|
+
const entries: TranscriptEntry[] = [];
|
|
83
|
+
for (const line of readFileSync(transcriptPath, "utf-8").split("\n")) {
|
|
84
|
+
if (!line.trim()) continue;
|
|
85
|
+
try {
|
|
86
|
+
entries.push(JSON.parse(line));
|
|
87
|
+
} catch {
|
|
88
|
+
// Skip partially written lines
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let start = -1;
|
|
93
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
94
|
+
if (isUserPrompt(entries[i])) {
|
|
95
|
+
start = i;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const turnEntries = entries.slice(start + 1).filter((e) => !e.isSidechain);
|
|
100
|
+
|
|
101
|
+
const uses = new Map<string, ToolUse>();
|
|
102
|
+
for (const entry of turnEntries) {
|
|
103
|
+
const content = entry.message?.content;
|
|
104
|
+
if (!Array.isArray(content)) continue;
|
|
105
|
+
for (const block of content) {
|
|
106
|
+
if (block.type === "tool_use" && block.id && block.name) {
|
|
107
|
+
uses.set(block.id, { name: block.name, input: block.input ?? {}, isError: false });
|
|
108
|
+
} else if (block.type === "tool_result" && block.tool_use_id && block.is_error) {
|
|
109
|
+
const use = uses.get(block.tool_use_id);
|
|
110
|
+
if (use) use.isError = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
id: start >= 0 ? entries[start].uuid ?? String(start) : "start",
|
|
117
|
+
entries: turnEntries,
|
|
118
|
+
toolUses: [...uses.values()],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function editedFiles(toolUses: ToolUse[]): string[] {
|
|
123
|
+
const files = toolUses
|
|
124
|
+
.filter((t) => EDIT_TOOLS.includes(t.name))
|
|
125
|
+
.map((t) => (t.input.file_path ?? t.input.notebook_path) as string | undefined)
|
|
126
|
+
.filter((f): f is string => !!f);
|
|
127
|
+
return [...new Set(files)];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Code text written in this turn, keyed by file
|
|
131
|
+
export function writtenCode(toolUses: ToolUse[]): { file: string; code: string }[] {
|
|
132
|
+
const out: { file: string; code: string }[] = [];
|
|
133
|
+
for (const t of toolUses) {
|
|
134
|
+
const file = String(t.input.file_path ?? "");
|
|
135
|
+
if (t.name === "Write") out.push({ file, code: String(t.input.content ?? "") });
|
|
136
|
+
else if (t.name === "Edit") out.push({ file, code: String(t.input.new_string ?? "") });
|
|
137
|
+
else if (t.name === "MultiEdit" && Array.isArray(t.input.edits)) {
|
|
138
|
+
for (const e of t.input.edits as { new_string?: string }[]) {
|
|
139
|
+
out.push({ file, code: String(e.new_string ?? "") });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// "mcp__<server>__<tool>" → "<server>"
|
|
147
|
+
export function mcpServer(toolName: string): string | undefined {
|
|
148
|
+
return toolName.startsWith("mcp__") ? toolName.split("__")[1] : undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Stop hook: keep Claude working with `reason` as its next instruction.
|
|
152
|
+
// Never block twice in a row (stop_hook_active) to avoid infinite loops.
|
|
153
|
+
export function blockOrNotify(input: HookInput, reason: string): void {
|
|
154
|
+
if (input.stop_hook_active) {
|
|
155
|
+
console.log(JSON.stringify({ systemMessage: reason }));
|
|
156
|
+
} else {
|
|
157
|
+
console.log(JSON.stringify({ decision: "block", reason }));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function notify(message: string): void {
|
|
162
|
+
console.log(JSON.stringify({ systemMessage: message }));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function done(): never {
|
|
166
|
+
console.log(JSON.stringify({}));
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|