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.
Files changed (47) hide show
  1. package/.claude/QUICK-REFERENCE.md +213 -213
  2. package/.claude/cost-dashboard.md +162 -162
  3. package/.claude/docs/COST-OPTIMIZATION.md +402 -403
  4. package/.claude/docs/MCP-INTEGRATION-GUIDE.md +348 -348
  5. package/.claude/docs/PLAN-MODE-OPTIMIZATION.md +835 -836
  6. package/.claude/hooks/auto-learning-review.ts +200 -195
  7. package/.claude/hooks/auto-lint-fix.ts +60 -104
  8. package/.claude/hooks/auto-security-review.ts +314 -354
  9. package/.claude/hooks/auto-validate.ts +249 -348
  10. package/.claude/hooks/lib/claude-hook.ts +168 -0
  11. package/.claude/hooks/plan-quality-tracker.ts +598 -719
  12. package/.claude/rules/anti-patterns.md +22 -22
  13. package/.claude/rules/context-tier-1.md +41 -41
  14. package/.claude/rules/context-tier-2.md +285 -285
  15. package/.claude/rules/general.md +25 -25
  16. package/.claude/rules/mcp-auto-use.md +46 -46
  17. package/.claude/rules/parallel-exploration-auto.md +30 -31
  18. package/.claude/rules/plan-mode-execution.md +289 -289
  19. package/.claude/rules/plan-mode-research.md +34 -34
  20. package/.claude/rules/plan-mode-structure.md +166 -166
  21. package/.claude/rules/plan-mode-validation.md +129 -130
  22. package/.claude/rules/security-critical.md +19 -19
  23. package/.claude/rules/tool-auto-selection.md +45 -45
  24. package/.claude/rules/tool-strategy.md +22 -22
  25. package/.claude/scripts/security-scan.js +1 -1
  26. package/.claude/security-dashboard.md +142 -142
  27. package/.claude/settings.json +50 -0
  28. package/.claude/skills/debug-optimizer/SKILL.md +420 -399
  29. package/.claude/skills/learning-loop/SKILL.md +313 -308
  30. package/.claude/skills/mcp-mastery/SKILL.md +218 -214
  31. package/.claude/skills/nextjs-guide/SKILL.md +687 -687
  32. package/.claude/skills/parallel-exploration/SKILL.md +197 -193
  33. package/.claude/skills/plan-mode-mastery/SKILL.md +268 -263
  34. package/.claude/skills/python-guide/SKILL.md +822 -822
  35. package/.claude/skills/react-guide/SKILL.md +762 -762
  36. package/.claude/skills/{security.md → security-guide/SKILL.md} +10 -0
  37. package/.claude/skills/solidity-guide/SKILL.md +5 -0
  38. package/.claude/skills/tool-selection/SKILL.md +433 -428
  39. package/.claude/skills/vite-guide/SKILL.md +445 -445
  40. package/CLAUDE.md +1 -0
  41. package/README.md +74 -35
  42. package/README.vi.md +106 -0
  43. package/bin/cli.js +13 -4
  44. package/package.json +9 -1
  45. package/.claude/skills/debug-optimizer.md +0 -12
  46. package/.claude/skills/nextjs.md +0 -13
  47. 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
+ }