qualitative-research-pro 1.0.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.
Files changed (114) hide show
  1. package/AGENTS.md +108 -0
  2. package/CLAUDE.md +171 -0
  3. package/LICENSE +21 -0
  4. package/README.md +166 -0
  5. package/agents/analysis-orchestrator.md +162 -0
  6. package/agents/audit-trail-builder.md +127 -0
  7. package/agents/category-developer.md +179 -0
  8. package/agents/citation-manager.md +83 -0
  9. package/agents/constant-comparator.md +135 -0
  10. package/agents/data-manager.md +104 -0
  11. package/agents/discussion-writer.md +128 -0
  12. package/agents/document-analyst.md +114 -0
  13. package/agents/ethics-reviewer.md +119 -0
  14. package/agents/field-note-analyst.md +124 -0
  15. package/agents/fit-assessor.md +192 -0
  16. package/agents/grounded-theorist.md +210 -0
  17. package/agents/literature-integrator.md +169 -0
  18. package/agents/literature-reviewer.md +112 -0
  19. package/agents/memo-writer.md +234 -0
  20. package/agents/methodology-critic.md +166 -0
  21. package/agents/methods-writer.md +109 -0
  22. package/agents/open-coder.md +187 -0
  23. package/agents/pattern-analyst.md +166 -0
  24. package/agents/peer-reviewer.md +129 -0
  25. package/agents/planner.md +122 -0
  26. package/agents/proposal-writer.md +108 -0
  27. package/agents/reflexivity-auditor.md +128 -0
  28. package/agents/research-designer.md +164 -0
  29. package/agents/research-writer.md +100 -0
  30. package/agents/saturation-assessor.md +159 -0
  31. package/agents/selective-coder.md +167 -0
  32. package/agents/theoretical-coder.md +260 -0
  33. package/agents/theoretical-sampler.md +165 -0
  34. package/agents/transcript-analyst.md +123 -0
  35. package/bin/cli.mjs +236 -0
  36. package/hooks/dist/agent-memory-loader.mjs +94 -0
  37. package/hooks/dist/agent-memory-saver.mjs +113 -0
  38. package/hooks/dist/bash-audit-log.mjs +71 -0
  39. package/hooks/dist/credential-deny.mjs +165 -0
  40. package/hooks/dist/forge-compile-check.mjs +92 -0
  41. package/hooks/dist/gas-snapshot-diff.mjs +71 -0
  42. package/hooks/dist/memory-awareness.mjs +276 -0
  43. package/hooks/dist/natspec-enforcer.mjs +67 -0
  44. package/hooks/dist/passive-learner.mjs +220 -0
  45. package/hooks/dist/pre-compact-continuity.mjs +467 -0
  46. package/hooks/dist/sast-on-edit.mjs +230 -0
  47. package/hooks/dist/session-analytics.mjs +84 -0
  48. package/hooks/dist/session-end-cleanup.mjs +121 -0
  49. package/hooks/dist/session-outcome.mjs +84 -0
  50. package/hooks/dist/session-register.mjs +307 -0
  51. package/hooks/dist/session-start-continuity.mjs +405 -0
  52. package/hooks/dist/slither-on-save.mjs +87 -0
  53. package/hooks/dist/storage-layout-check.mjs +89 -0
  54. package/hooks/dist/transcript-parser.mjs +214 -0
  55. package/install.sh +194 -0
  56. package/package.json +46 -0
  57. package/plugin.json +19 -0
  58. package/rules/academic-writing-style.md +42 -0
  59. package/rules/citation-standards.md +47 -0
  60. package/rules/current-methodological-state.md +40 -0
  61. package/rules/data-handling.md +44 -0
  62. package/rules/finding-output-format.md +47 -0
  63. package/rules/gt-coding-standards.md +40 -0
  64. package/rules/methodological-rigor.md +56 -0
  65. package/rules/quality-criteria.md +41 -0
  66. package/rules/reflexivity-requirements.md +40 -0
  67. package/rules/research-ethics-standards.md +44 -0
  68. package/skills/.gitkeep +2 -0
  69. package/skills/academic-writing/SKILL.md +73 -0
  70. package/skills/action-research/SKILL.md +96 -0
  71. package/skills/apa-formatting/SKILL.md +85 -0
  72. package/skills/case-study-methods/SKILL.md +96 -0
  73. package/skills/category-development/SKILL.md +80 -0
  74. package/skills/chicago-formatting/SKILL.md +81 -0
  75. package/skills/coding-pipeline/SKILL.md +81 -0
  76. package/skills/conceptual-frameworks/SKILL.md +70 -0
  77. package/skills/constant-comparison/SKILL.md +188 -0
  78. package/skills/constructivist-gt/SKILL.md +91 -0
  79. package/skills/data-management-protocols/SKILL.md +67 -0
  80. package/skills/document-analysis/SKILL.md +66 -0
  81. package/skills/ethnographic-methods/SKILL.md +82 -0
  82. package/skills/focus-group-methods/SKILL.md +66 -0
  83. package/skills/formal-theory/SKILL.md +159 -0
  84. package/skills/glaserian-grounded-theory/SKILL.md +212 -0
  85. package/skills/interview-design/SKILL.md +67 -0
  86. package/skills/literature-synthesis/SKILL.md +71 -0
  87. package/skills/member-checking/SKILL.md +66 -0
  88. package/skills/memo-writing/SKILL.md +158 -0
  89. package/skills/mixed-methods-design/SKILL.md +69 -0
  90. package/skills/narrative-inquiry/SKILL.md +101 -0
  91. package/skills/observation-methods/SKILL.md +67 -0
  92. package/skills/open-coding/SKILL.md +176 -0
  93. package/skills/paradigmatic-positioning/SKILL.md +72 -0
  94. package/skills/peer-debriefing/SKILL.md +72 -0
  95. package/skills/phenomenological-methods/SKILL.md +91 -0
  96. package/skills/qualitative-rigor/SKILL.md +78 -0
  97. package/skills/reflexive-practice/SKILL.md +64 -0
  98. package/skills/research-ethics/SKILL.md +64 -0
  99. package/skills/research-proposal-writing/SKILL.md +81 -0
  100. package/skills/research-questions/SKILL.md +66 -0
  101. package/skills/sampling-strategies/SKILL.md +61 -0
  102. package/skills/selective-coding/SKILL.md +183 -0
  103. package/skills/situational-analysis/SKILL.md +93 -0
  104. package/skills/substantive-theory/SKILL.md +169 -0
  105. package/skills/thematic-analysis/SKILL.md +80 -0
  106. package/skills/theoretical-coding/SKILL.md +213 -0
  107. package/skills/theoretical-sampling/SKILL.md +152 -0
  108. package/skills/theoretical-saturation/SKILL.md +179 -0
  109. package/skills/theoretical-sensitivity/SKILL.md +175 -0
  110. package/skills/theory-integration/SKILL.md +85 -0
  111. package/skills/thick-description/SKILL.md +69 -0
  112. package/skills/triangulation/SKILL.md +65 -0
  113. package/skills/visual-modeling/SKILL.md +66 -0
  114. package/skills/vulnerable-populations/SKILL.md +69 -0
package/bin/cli.mjs ADDED
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Qualitative Research Pro npm CLI
4
+ // npx qualitative-research-pro [command] [options]
5
+
6
+ import { existsSync, mkdirSync, cpSync, readdirSync, statSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import { homedir } from 'node:os';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { createInterface } from 'node:readline';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const REPO_DIR = join(__dirname, '..');
15
+ const CLAUDE_DIR = join(homedir(), '.claude');
16
+ const VERSION = '1.0.0';
17
+
18
+ const COLORS = {
19
+ reset: '\x1b[0m',
20
+ bold: '\x1b[1m',
21
+ dim: '\x1b[2m',
22
+ green: '\x1b[32m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ cyan: '\x1b[36m',
26
+ red: '\x1b[31m',
27
+ };
28
+
29
+ function c(color, text) { return `${COLORS[color]}${text}${COLORS.reset}`; }
30
+
31
+ function ask(question) {
32
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
33
+ return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer); }));
34
+ }
35
+
36
+ function countFiles(dir, pattern) {
37
+ try {
38
+ if (pattern === 'dirs') return readdirSync(dir).filter(f => statSync(join(dir, f)).isDirectory()).length;
39
+ return readdirSync(dir).filter(f => f.endsWith(pattern)).length;
40
+ } catch { return 0; }
41
+ }
42
+
43
+ function smartCopy(src, dest, force = false) {
44
+ let added = 0, skipped = 0;
45
+ if (!existsSync(src)) return { added, skipped };
46
+
47
+ if (statSync(src).isDirectory()) {
48
+ if (force || !existsSync(dest)) {
49
+ mkdirSync(dirname(dest), { recursive: true });
50
+ cpSync(src, dest, { recursive: true });
51
+ added++;
52
+ } else { skipped++; }
53
+ } else {
54
+ if (force || !existsSync(dest)) {
55
+ mkdirSync(dirname(dest), { recursive: true });
56
+ cpSync(src, dest);
57
+ added++;
58
+ } else { skipped++; }
59
+ }
60
+ return { added, skipped };
61
+ }
62
+
63
+ function install(options = {}) {
64
+ const { force = false, profile = 'all' } = options;
65
+ let totalAdded = 0, totalSkipped = 0;
66
+
67
+ const track = (result) => { totalAdded += result.added; totalSkipped += result.skipped; };
68
+
69
+ const count = (dir, opts = {}) => {
70
+ const p = join(REPO_DIR, dir);
71
+ if (!existsSync(p)) return 0;
72
+ const entries = readdirSync(p);
73
+ if (opts.dirs) return entries.filter(e => statSync(join(p, e)).isDirectory()).length;
74
+ return entries.filter(e => e.endsWith(opts.ext || '.md')).length;
75
+ };
76
+ const agents = count('agents');
77
+ const skills = count('skills', { dirs: true });
78
+ const hooks = count('hooks/src', { ext: '.ts' });
79
+ const rules = count('rules');
80
+
81
+ console.log(c('bold', '\nQualitative Research Pro installer'));
82
+ console.log('========================\n');
83
+ console.log(`Installing into ${c('cyan', '~/.claude/')}:`);
84
+ console.log(` - ${agents} agents -> ~/.claude/agents/`);
85
+ console.log(` - ${skills} skills -> ~/.claude/skills/`);
86
+ console.log(` - ${hooks} hooks -> ~/.claude/hooks/`);
87
+ console.log(` - ${rules} rules -> ~/.claude/rules/`);
88
+ console.log(`\nMode: ${force ? c('yellow', 'OVERWRITE') : c('green', 'MERGE')} | Profile: ${c('blue', profile)}\n`);
89
+
90
+ process.stdout.write('Installing agents...');
91
+ mkdirSync(join(CLAUDE_DIR, 'agents'), { recursive: true });
92
+ const agentDir = join(REPO_DIR, 'agents');
93
+ if (existsSync(agentDir)) {
94
+ for (const f of readdirSync(agentDir).filter(f => f.endsWith('.md'))) {
95
+ track(smartCopy(join(agentDir, f), join(CLAUDE_DIR, 'agents', f), force));
96
+ }
97
+ }
98
+ console.log(c('green', ' done'));
99
+
100
+ process.stdout.write('Installing skills...');
101
+ mkdirSync(join(CLAUDE_DIR, 'skills'), { recursive: true });
102
+ const skillDir = join(REPO_DIR, 'skills');
103
+ if (existsSync(skillDir)) {
104
+ for (const d of readdirSync(skillDir)) {
105
+ const fullPath = join(skillDir, d);
106
+ if (statSync(fullPath).isDirectory()) {
107
+ track(smartCopy(fullPath, join(CLAUDE_DIR, 'skills', d), force));
108
+ }
109
+ }
110
+ }
111
+ console.log(c('green', ' done'));
112
+
113
+ process.stdout.write('Installing hooks...');
114
+ mkdirSync(join(CLAUDE_DIR, 'hooks', 'dist'), { recursive: true });
115
+ const distDir = join(REPO_DIR, 'hooks', 'dist');
116
+ if (existsSync(distDir)) {
117
+ for (const f of readdirSync(distDir).filter(f => f.endsWith('.mjs'))) {
118
+ track(smartCopy(join(distDir, f), join(CLAUDE_DIR, 'hooks', 'dist', f), force));
119
+ }
120
+ }
121
+ const hooksJson = join(REPO_DIR, 'hooks', 'hooks.json');
122
+ if (existsSync(hooksJson)) {
123
+ track(smartCopy(hooksJson, join(CLAUDE_DIR, 'hooks', 'hooks.json'), force));
124
+ }
125
+ console.log(c('green', ' done'));
126
+
127
+ process.stdout.write('Installing rules...');
128
+ mkdirSync(join(CLAUDE_DIR, 'rules'), { recursive: true });
129
+ const rulesDir = join(REPO_DIR, 'rules');
130
+ if (existsSync(rulesDir)) {
131
+ for (const f of readdirSync(rulesDir).filter(f => f.endsWith('.md'))) {
132
+ track(smartCopy(join(rulesDir, f), join(CLAUDE_DIR, 'rules', f), force));
133
+ }
134
+ }
135
+ console.log(c('green', ' done'));
136
+
137
+ console.log(`\n${c('bold', 'Installation complete!')}`);
138
+ console.log(` ${c('green', 'Added:')} ${totalAdded} files`);
139
+ console.log(` ${c('dim', 'Skipped:')} ${totalSkipped} files (already existed)`);
140
+ console.log(`\n Agents: ${countFiles(join(CLAUDE_DIR, 'agents'), '.md')}`);
141
+ console.log(` Skills: ${countFiles(join(CLAUDE_DIR, 'skills'), 'dirs')}`);
142
+ console.log(` Hooks: ${countFiles(join(CLAUDE_DIR, 'hooks', 'dist'), '.mjs')}`);
143
+ console.log(` Rules: ${countFiles(join(CLAUDE_DIR, 'rules'), '.md')}`);
144
+
145
+ if (totalSkipped > 0) {
146
+ console.log(`\n Tip: Use ${c('yellow', 'npx qualitative-research-pro init --force')} to overwrite existing files.`);
147
+ }
148
+
149
+ console.log(`\n Run ${c('cyan', 'npx qualitative-research-pro doctor')} to verify your setup.\n`);
150
+ }
151
+
152
+ function showHelp() {
153
+ const cnt = (dir, opts = {}) => {
154
+ const p = join(REPO_DIR, dir);
155
+ if (!existsSync(p)) return '?';
156
+ const entries = readdirSync(p);
157
+ if (opts.dirs) return entries.filter(e => statSync(join(p, e)).isDirectory()).length;
158
+ return entries.filter(e => e.endsWith(opts.ext || '.md')).length;
159
+ };
160
+ console.log(`
161
+ ${c('bold', 'Qualitative Research Pro')} v${VERSION} - Academic qualitative research squad
162
+
163
+ ${c('bold', 'USAGE')}
164
+ npx qualitative-research-pro [command] [options]
165
+
166
+ ${c('bold', 'COMMANDS')}
167
+ init Install Qualitative Research Pro into ~/.claude/
168
+ init --force Overwrite existing files
169
+ doctor Run health check on installation
170
+ version Show version
171
+ help Show this help
172
+
173
+ ${c('bold', 'EXAMPLES')}
174
+ npx qualitative-research-pro init # Install all components
175
+ npx qualitative-research-pro init --force # Overwrite existing setup
176
+ npx qualitative-research-pro doctor # Verify installation
177
+
178
+ ${c('bold', 'COMPONENTS')}
179
+ ${cnt('agents')} agents Grounded theory, qualitative analysis, writing, review
180
+ ${cnt('skills', { dirs: true })} skills Deep methodology knowledge across qualitative traditions
181
+ ${cnt('hooks/src', { ext: '.ts' })} hooks Research quality checks (citations, methodology)
182
+ ${cnt('rules')} rules Academic research methodology guidelines
183
+
184
+ ${c('dim', 'https://github.com/ccashwell/qualitative-research-pro')}
185
+ `);
186
+ }
187
+
188
+ function doctor() {
189
+ console.log(`\n${c('bold', 'Qualitative Research Pro doctor')} - Health Check\n`);
190
+ const checks = [
191
+ { name: 'Agents directory', path: join(CLAUDE_DIR, 'agents'), type: 'dir' },
192
+ { name: 'Skills directory', path: join(CLAUDE_DIR, 'skills'), type: 'dir' },
193
+ { name: 'Hooks dist', path: join(CLAUDE_DIR, 'hooks', 'dist'), type: 'dir' },
194
+ { name: 'Rules directory', path: join(CLAUDE_DIR, 'rules'), type: 'dir' },
195
+ ];
196
+
197
+ let pass = 0, fail = 0;
198
+ for (const check of checks) {
199
+ const exists = existsSync(check.path);
200
+ const status = exists ? c('green', 'PASS') : c('red', 'FAIL');
201
+ console.log(` ${status} ${check.name}`);
202
+ if (exists) pass++; else fail++;
203
+ }
204
+
205
+ console.log(`\n ${pass} passed, ${fail} failed\n`);
206
+ if (fail > 0) {
207
+ console.log(` Run ${c('cyan', 'npx qualitative-research-pro init')} to fix.\n`);
208
+ }
209
+ }
210
+
211
+ const args = process.argv.slice(2);
212
+ const command = args[0] || 'help';
213
+
214
+ switch (command) {
215
+ case 'init': {
216
+ const force = args.includes('--force');
217
+ const profileIdx = args.indexOf('--profile');
218
+ const profile = profileIdx !== -1 ? args[profileIdx + 1] : 'all';
219
+ install({ force, profile });
220
+ break;
221
+ }
222
+ case 'doctor':
223
+ doctor();
224
+ break;
225
+ case 'version':
226
+ case '--version':
227
+ case '-v':
228
+ console.log(`Qualitative Research Pro v${VERSION}`);
229
+ break;
230
+ case 'help':
231
+ case '--help':
232
+ case '-h':
233
+ default:
234
+ showHelp();
235
+ break;
236
+ }
@@ -0,0 +1,94 @@
1
+ // src/agent-memory-loader.ts
2
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ var CLAUDE_HOME = join(homedir(), ".claude");
6
+ var MAX_MEMORY_SIZE = 8e3;
7
+ var MAX_FILES = 10;
8
+ function sanitizeAgentType(agentType) {
9
+ return agentType.replace(/[:/\\]/g, "-").replace(/\s+/g, "-").toLowerCase();
10
+ }
11
+ function getAgentMemoryDir(agentType, scope) {
12
+ const dirName = sanitizeAgentType(agentType);
13
+ switch (scope) {
14
+ case "user":
15
+ return join(CLAUDE_HOME, "agent-memory", dirName);
16
+ case "project":
17
+ return join(process.cwd(), ".claude", "agent-memory", dirName);
18
+ case "local":
19
+ return join(process.cwd(), ".claude", "agent-memory-local", dirName);
20
+ }
21
+ }
22
+ function scanMemoryDir(dir) {
23
+ if (!existsSync(dir)) return [];
24
+ const results = [];
25
+ try {
26
+ const files = readdirSync(dir).filter((f) => f.endsWith(".md")).slice(0, MAX_FILES);
27
+ for (const file of files) {
28
+ const filePath = join(dir, file);
29
+ try {
30
+ const stat = statSync(filePath);
31
+ if (stat.isFile() && stat.size > 0 && stat.size < 5e4) {
32
+ const content = readFileSync(filePath, "utf-8").trim();
33
+ if (content) {
34
+ results.push(`### ${file}
35
+ ${content}`);
36
+ }
37
+ }
38
+ } catch {
39
+ }
40
+ }
41
+ } catch {
42
+ }
43
+ return results;
44
+ }
45
+ function getAgentMemoryScope(agentType) {
46
+ const agentDir = join(CLAUDE_HOME, "agents");
47
+ const agentFile = join(agentDir, `${agentType}.md`);
48
+ if (!existsSync(agentFile)) return void 0;
49
+ try {
50
+ const content = readFileSync(agentFile, "utf-8");
51
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
52
+ if (!frontmatterMatch) return void 0;
53
+ const memoryMatch = frontmatterMatch[1].match(/^memory:\s*(.+)$/m);
54
+ if (!memoryMatch) return void 0;
55
+ const scope = memoryMatch[1].trim().toLowerCase();
56
+ if (scope === "user" || scope === "project" || scope === "local") {
57
+ return scope;
58
+ }
59
+ } catch {
60
+ }
61
+ return void 0;
62
+ }
63
+ function main() {
64
+ try {
65
+ const input = JSON.parse(process.argv[2] || "{}");
66
+ if (input.tool_name !== "Agent") return;
67
+ const agentType = input.tool_input?.subagent_type;
68
+ if (!agentType) return;
69
+ const scope = getAgentMemoryScope(agentType);
70
+ const effectiveScope = scope || "user";
71
+ const memoryDir = getAgentMemoryDir(agentType, effectiveScope);
72
+ const memories = scanMemoryDir(memoryDir);
73
+ if (memories.length === 0) return;
74
+ let combined = memories.join("\n\n");
75
+ if (combined.length > MAX_MEMORY_SIZE) {
76
+ combined = combined.substring(0, MAX_MEMORY_SIZE) + "\n\n[... truncated]";
77
+ }
78
+ const context = `## Agent Persistent Memory (${agentType}, scope: ${effectiveScope})
79
+
80
+ Bu agent'in onceki session'lardan biriktirdigi kalici bellek:
81
+
82
+ ${combined}
83
+
84
+ ---
85
+ Bu memory'yi guncelle: Yeni ogrenimler varsa ~/.claude/agent-memory/${sanitizeAgentType(agentType)}/ dizinine yaz.
86
+ Memory guncelleme zorunlu DEGiL - sadece gercekten yeni ve degerli bilgi varsa kaydet.`;
87
+ const result = {
88
+ additionalContext: context
89
+ };
90
+ process.stdout.write(JSON.stringify(result));
91
+ } catch {
92
+ }
93
+ }
94
+ main();
@@ -0,0 +1,113 @@
1
+ // src/agent-memory-saver.ts
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ var CLAUDE_HOME = join(homedir(), ".claude");
6
+ var LEARNING_SIGNALS = [
7
+ /\bfound\b.*\bbug\b/i,
8
+ /\broot cause\b/i,
9
+ /\bfixed\b.*\bissue\b/i,
10
+ /\bpattern\b.*\bdetected\b/i,
11
+ /\bsecurity\b.*\b(vuln|issue|risk)\b/i,
12
+ /\bperformance\b.*\b(issue|bottleneck|slow)\b/i,
13
+ /\barchitectur(e|al)\b.*\b(decision|change|pattern)\b/i,
14
+ /\bVERDICT:\s*(FAIL|WARN)\b/i,
15
+ /\bCRITICAL\b/i,
16
+ /\bbreaking change\b/i,
17
+ /\bdeprecated\b/i,
18
+ /\bmigrat(e|ion)\b/i,
19
+ /\berror.*handl/i,
20
+ /\brace condition\b/i,
21
+ /\bmemory leak\b/i
22
+ ];
23
+ var SUCCESS_SIGNALS = [
24
+ /\bVERDICT:\s*PASS\b/i,
25
+ /\ball tests pass/i,
26
+ /\bbuild succeed/i,
27
+ /\bimplemented\b/i,
28
+ /\brefactored\b/i
29
+ ];
30
+ function sanitizeAgentType(agentType) {
31
+ return agentType.replace(/[:/\\]/g, "-").replace(/\s+/g, "-").toLowerCase();
32
+ }
33
+ function ensureDir(dir) {
34
+ if (!existsSync(dir)) {
35
+ mkdirSync(dir, { recursive: true });
36
+ }
37
+ }
38
+ function getTimestamp() {
39
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").substring(0, 19);
40
+ }
41
+ function hasLearningSignal(output) {
42
+ return LEARNING_SIGNALS.some((pattern) => pattern.test(output));
43
+ }
44
+ function hasSuccessSignal(output) {
45
+ return SUCCESS_SIGNALS.some((pattern) => pattern.test(output));
46
+ }
47
+ function extractKeyInfo(output, maxLen = 500) {
48
+ const lines = output.split("\n");
49
+ const keyLines = [];
50
+ for (const line of lines) {
51
+ const trimmed = line.trim();
52
+ if (!trimmed) continue;
53
+ if (/VERDICT|CRITICAL|WARN|FAIL|ERROR|FOUND|PATTERN|DECISION|BUG|FIX|ROOT CAUSE/i.test(trimmed) || trimmed.startsWith("- ") || trimmed.startsWith("* ") || trimmed.startsWith("##")) {
54
+ keyLines.push(trimmed);
55
+ }
56
+ }
57
+ let result = keyLines.join("\n");
58
+ if (result.length > maxLen) {
59
+ result = result.substring(0, maxLen) + "...";
60
+ }
61
+ return result || output.substring(0, maxLen);
62
+ }
63
+ function main() {
64
+ try {
65
+ const input = JSON.parse(process.argv[2] || "{}");
66
+ if (input.tool_name !== "Agent") return;
67
+ const agentType = input.tool_input?.subagent_type;
68
+ const output = input.tool_output || "";
69
+ const description = input.tool_input?.description || "";
70
+ if (!agentType || !output || output.length < 50) return;
71
+ const hasLearning = hasLearningSignal(output);
72
+ const hasSuccess = hasSuccessSignal(output);
73
+ if (!hasLearning && !hasSuccess) return;
74
+ const dirName = sanitizeAgentType(agentType);
75
+ const memoryDir = join(CLAUDE_HOME, "agent-memory", dirName);
76
+ ensureDir(memoryDir);
77
+ const timestamp = getTimestamp();
78
+ const keyInfo = extractKeyInfo(output);
79
+ if (hasLearning) {
80
+ const journalPath = join(memoryDir, "learnings.md");
81
+ const entry = `
82
+ ## ${timestamp} - ${description || "Agent task"}
83
+
84
+ ${keyInfo}
85
+
86
+ ---
87
+ `;
88
+ appendFileSync(journalPath, entry, "utf-8");
89
+ try {
90
+ const content = readFileSync(journalPath, "utf-8");
91
+ if (content.length > 5e4) {
92
+ const truncated = content.substring(content.length - 4e4);
93
+ const firstNewline = truncated.indexOf("\n## ");
94
+ writeFileSync(journalPath, firstNewline > 0 ? truncated.substring(firstNewline) : truncated, "utf-8");
95
+ }
96
+ } catch {
97
+ }
98
+ }
99
+ const memoryMdPath = join(memoryDir, "MEMORY.md");
100
+ if (!existsSync(memoryMdPath)) {
101
+ const initialContent = `# ${agentType} Agent Memory
102
+
103
+ Bu dosya ${agentType} agent'inin kalici bellegidir. Otomatik guncellenir.
104
+
105
+ ## Ogrenimler
106
+ - [learnings.md](learnings.md) - Tespit edilen sorunlar, patternler, kararlar
107
+ `;
108
+ writeFileSync(memoryMdPath, initialContent, "utf-8");
109
+ }
110
+ } catch {
111
+ }
112
+ }
113
+ main();
@@ -0,0 +1,71 @@
1
+ // src/bash-audit-log.ts
2
+ import { readFileSync as readFileSync2, mkdirSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+
6
+ // src/shared/log-rotation.ts
7
+ import { statSync, readFileSync, writeFileSync, appendFileSync, renameSync, unlinkSync } from "fs";
8
+ function appendWithRotation(filePath, line, maxBytes = 2 * 1024 * 1024, keepLines = 5e3) {
9
+ appendFileSync(filePath, line);
10
+ try {
11
+ const stats = statSync(filePath);
12
+ if (stats.size > maxBytes) {
13
+ const tmpPath = filePath + ".rotating";
14
+ try {
15
+ renameSync(filePath, tmpPath);
16
+ const content = readFileSync(tmpPath, "utf-8");
17
+ const lines = content.split("\n").filter((l) => l.length > 0);
18
+ writeFileSync(filePath, lines.slice(-keepLines).join("\n") + "\n");
19
+ unlinkSync(tmpPath);
20
+ } catch {
21
+ }
22
+ }
23
+ } catch {
24
+ }
25
+ }
26
+
27
+ // src/bash-audit-log.ts
28
+ var WRITE_PATTERNS = [
29
+ /^(rm|mv|cp|mkdir|rmdir|touch|chmod|chown)\b/,
30
+ /^git\s+(push|commit|reset|checkout|merge|rebase|stash|clean|branch\s+-[dD])/,
31
+ /\b(npm|pnpm|yarn)\s+(install|uninstall|publish|run\s+build)/,
32
+ />(>)?/,
33
+ // redirect
34
+ /\bsudo\b/,
35
+ /\bdocker\s+(rm|stop|kill|exec|run)/
36
+ ];
37
+ function isWriteCommand(cmd) {
38
+ return WRITE_PATTERNS.some((p) => p.test(cmd.trim()));
39
+ }
40
+ function main() {
41
+ let raw = "";
42
+ try {
43
+ raw = readFileSync2(0, "utf-8");
44
+ } catch {
45
+ return;
46
+ }
47
+ if (!raw) return;
48
+ let input;
49
+ try {
50
+ input = JSON.parse(raw);
51
+ } catch {
52
+ console.log("{}");
53
+ return;
54
+ }
55
+ if (input.tool_name !== "Bash" || !input.tool_input?.command) {
56
+ console.log("{}");
57
+ return;
58
+ }
59
+ const cmd = input.tool_input.command;
60
+ const tag = isWriteCommand(cmd) ? "[WRITE]" : "[READ]";
61
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
62
+ const desc = input.tool_input.description || "";
63
+ const sessionId = input.session_id?.slice(0, 8) || "unknown";
64
+ const logDir = join(homedir(), ".claude");
65
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
66
+ const logLine = `${timestamp} ${tag} [${sessionId}] ${cmd}${desc ? ` # ${desc}` : ""}
67
+ `;
68
+ appendWithRotation(join(logDir, "bash-audit.log"), logLine);
69
+ console.log("{}");
70
+ }
71
+ main();
@@ -0,0 +1,165 @@
1
+ // src/credential-deny.ts
2
+ import { readFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ var HOME = homedir().replace(/\\/g, "/");
5
+ var DENIED_PATHS = [
6
+ { pattern: /[/\\]\.ssh[/\\]/, reason: "SSH keys" },
7
+ { pattern: /[/\\]\.aws[/\\]/, reason: "AWS credentials" },
8
+ { pattern: /[/\\]\.kube[/\\]/, reason: "Kubernetes config" },
9
+ { pattern: /[/\\]\.gnupg[/\\]/, reason: "GPG keys" },
10
+ { pattern: /[/\\]\.npmrc$/, reason: "NPM tokens" },
11
+ { pattern: /[/\\]\.pypirc$/, reason: "PyPI tokens" },
12
+ { pattern: /[/\\]\.docker[/\\]config\.json$/, reason: "Docker credentials" },
13
+ { pattern: /[/\\]\.netrc$/, reason: "Network credentials" },
14
+ { pattern: /[/\\]credentials\.json$/, reason: "Credential file" },
15
+ { pattern: /[/\\]service[-_]?account.*\.json$/i, reason: "Service account key" },
16
+ { pattern: /[/\\]\.env$/, reason: "Environment secrets" },
17
+ { pattern: /[/\\]\.env\.[\w.]+$/, reason: "Environment secrets" },
18
+ { pattern: /[/\\]id_rsa$/, reason: "Private SSH key" },
19
+ { pattern: /[/\\]id_ed25519$/, reason: "Private SSH key" },
20
+ { pattern: /[/\\]id_ecdsa$/, reason: "Private SSH key" },
21
+ { pattern: /terraform\.tfstate$/i, reason: "Terraform state (may contain secrets)" },
22
+ { pattern: /[/\\]\.git[/\\]config$/, reason: "Git config (may contain credential helpers)" },
23
+ { pattern: /[/\\]\.password-store[/\\]/, reason: "Password store" }
24
+ ];
25
+ var SENSITIVE_ENV_VARS = [
26
+ "AWS_ACCESS_KEY_ID",
27
+ "AWS_SECRET_ACCESS_KEY",
28
+ "AWS_SESSION_TOKEN",
29
+ "GITHUB_TOKEN",
30
+ "GH_TOKEN",
31
+ "OPENAI_API_KEY",
32
+ "ANTHROPIC_API_KEY",
33
+ "DATABASE_URL",
34
+ "DB_PASSWORD",
35
+ "SECRET_KEY",
36
+ "PRIVATE_KEY",
37
+ "NPM_TOKEN",
38
+ "PYPI_TOKEN"
39
+ ];
40
+ var SENSITIVE_KEYWORDS = [
41
+ { pattern: /\.ssh\b/, reason: "SSH directory reference" },
42
+ { pattern: /\.aws\b/, reason: "AWS directory reference" },
43
+ { pattern: /\.kube\b/, reason: "Kubernetes directory reference" },
44
+ { pattern: /\.gnupg\b/, reason: "GPG directory reference" },
45
+ { pattern: /\bid_rsa\b/, reason: "SSH private key reference" },
46
+ { pattern: /\bid_ed25519\b/, reason: "SSH private key reference" },
47
+ { pattern: /\bid_ecdsa\b/, reason: "SSH private key reference" }
48
+ ];
49
+ function checkFilePath(filePath) {
50
+ const normalized = filePath.replace(/\\/g, "/");
51
+ for (const deny of DENIED_PATHS) {
52
+ if (deny.pattern.test(normalized)) {
53
+ return deny.reason;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ function extractPaths(cmd) {
59
+ const paths = [];
60
+ let m;
61
+ const absRegex = /(?:^|\s|[<>=|"'])(~?\/[^\s;|&><"']+)/g;
62
+ while ((m = absRegex.exec(cmd)) !== null) {
63
+ let p = m[1].trim();
64
+ if (p.startsWith("~")) p = HOME + p.slice(1);
65
+ paths.push(p);
66
+ }
67
+ const relRegex = /(?:^|\s)(\.{1,2}\/[^\s;|&><"']+)/g;
68
+ while ((m = relRegex.exec(cmd)) !== null) {
69
+ paths.push(m[1]);
70
+ }
71
+ const bareRegex = /(?:^|\s)(\.(?:ssh|aws|kube|gnupg|env|npmrc|pypirc|netrc)\b[^\s;|&><"']*)/g;
72
+ while ((m = bareRegex.exec(cmd)) !== null) {
73
+ paths.push(m[1]);
74
+ }
75
+ const homeVarRegex = /\$(?:HOME|\{HOME\})(\/[^\s;|&><"']+)/g;
76
+ while ((m = homeVarRegex.exec(cmd)) !== null) {
77
+ paths.push(HOME + m[1]);
78
+ }
79
+ const quotedRegex = /['"](\/?[^'"]*(?:\.ssh|\.aws|\.kube|\.gnupg|\.env|credentials|id_rsa|id_ed25519|\.npmrc|\.pypirc|\.netrc)[^'"]*)['"]/gi;
80
+ while ((m = quotedRegex.exec(cmd)) !== null) {
81
+ paths.push(m[1]);
82
+ }
83
+ return paths;
84
+ }
85
+ function checkBashCommand(cmd) {
86
+ if (/^\s*(env|printenv|set)\s*$/.test(cmd) || /^\s*(env|printenv|set)\s*\|/.test(cmd) || /\bexport\s+-p\b/.test(cmd)) {
87
+ return "Env dump tum secret'lari ifsa eder";
88
+ }
89
+ for (const v of SENSITIVE_ENV_VARS) {
90
+ const escaped = v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
91
+ if (new RegExp(`\\$\\{?${escaped}\\}?`).test(cmd)) {
92
+ return `Hassas env var erisimi: ${v}`;
93
+ }
94
+ }
95
+ for (const kw of SENSITIVE_KEYWORDS) {
96
+ if (kw.pattern.test(cmd)) {
97
+ return `${kw.reason} (bash komutu ile)`;
98
+ }
99
+ }
100
+ const paths = extractPaths(cmd);
101
+ for (const p of paths) {
102
+ const reason = checkFilePath(p);
103
+ if (reason) return `${reason} (bash komutu ile)`;
104
+ }
105
+ return null;
106
+ }
107
+ function block(reason) {
108
+ console.log(JSON.stringify({
109
+ decision: "block",
110
+ reason: `ENGELLENDI: ${reason}. Guvenlik kurali.`
111
+ }));
112
+ }
113
+ function main() {
114
+ let raw = "";
115
+ try {
116
+ raw = readFileSync(0, "utf-8");
117
+ } catch {
118
+ return;
119
+ }
120
+ if (!raw) {
121
+ console.log("{}");
122
+ return;
123
+ }
124
+ let input;
125
+ try {
126
+ input = JSON.parse(raw);
127
+ } catch {
128
+ block("Malformed hook input - fail closed");
129
+ return;
130
+ }
131
+ if (["Read", "Edit", "Write"].includes(input.tool_name) && input.tool_input?.file_path) {
132
+ const reason = checkFilePath(input.tool_input.file_path);
133
+ if (reason) {
134
+ block(`${reason} dosyasina erisim yasak`);
135
+ return;
136
+ }
137
+ }
138
+ if (input.tool_name === "Bash" && input.tool_input?.command) {
139
+ const reason = checkBashCommand(input.tool_input.command);
140
+ if (reason) {
141
+ block(reason);
142
+ return;
143
+ }
144
+ }
145
+ if (input.tool_name === "Grep" && input.tool_input?.path) {
146
+ const searchPath = input.tool_input.path;
147
+ const reason = checkFilePath(searchPath + "/") || checkFilePath(searchPath);
148
+ if (reason) {
149
+ block(`${reason} - Grep ile erisim yasak`);
150
+ return;
151
+ }
152
+ }
153
+ if (input.tool_name === "Glob") {
154
+ const globPath = input.tool_input?.path || "";
155
+ const globPattern = input.tool_input?.pattern || "";
156
+ const fullPath = globPath ? `${globPath}/${globPattern}` : globPattern;
157
+ const reason = checkFilePath(fullPath + "/") || checkFilePath(fullPath);
158
+ if (reason) {
159
+ block(`${reason} - Glob ile erisim yasak`);
160
+ return;
161
+ }
162
+ }
163
+ console.log("{}");
164
+ }
165
+ main();