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
@@ -0,0 +1,307 @@
1
+ // src/session-register.ts
2
+ import { readFileSync as readFileSync3 } from "fs";
3
+
4
+ // src/shared/db-utils-pg.ts
5
+ import { spawnSync } from "child_process";
6
+
7
+ // src/shared/opc-path.ts
8
+ import { existsSync } from "fs";
9
+ import { join } from "path";
10
+ function getOpcDir() {
11
+ const envOpcDir = process.env.CLAUDE_OPC_DIR;
12
+ if (envOpcDir && existsSync(envOpcDir)) {
13
+ return envOpcDir;
14
+ }
15
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
16
+ const localOpc = join(projectDir, "opc");
17
+ if (existsSync(localOpc)) {
18
+ return localOpc;
19
+ }
20
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
21
+ if (homeDir) {
22
+ const globalClaude = join(homeDir, ".claude");
23
+ const globalScripts = join(globalClaude, "scripts", "core");
24
+ if (existsSync(globalScripts)) {
25
+ return globalClaude;
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ function requireOpcDir() {
31
+ const opcDir = getOpcDir();
32
+ if (!opcDir) {
33
+ console.log(JSON.stringify({ result: "continue" }));
34
+ process.exit(0);
35
+ }
36
+ return opcDir;
37
+ }
38
+
39
+ // src/shared/db-utils-pg.ts
40
+ function getPgConnectionString() {
41
+ return process.env.CONTINUOUS_CLAUDE_DB_URL || process.env.DATABASE_URL || process.env.OPC_POSTGRES_URL || "postgresql://claude:claude_dev@localhost:5432/continuous_claude";
42
+ }
43
+ function runPgQuery(pythonCode, args = []) {
44
+ const opcDir = requireOpcDir();
45
+ const wrappedCode = `
46
+ import sys
47
+ import os
48
+ import asyncio
49
+ import json
50
+
51
+ # Add opc to path for imports
52
+ sys.path.insert(0, '${opcDir}')
53
+ os.chdir('${opcDir}')
54
+
55
+ ${pythonCode}
56
+ `;
57
+ try {
58
+ const result = spawnSync("uv", ["run", "python", "-c", wrappedCode, ...args], {
59
+ encoding: "utf-8",
60
+ maxBuffer: 1024 * 1024,
61
+ timeout: 5e3,
62
+ // 5 second timeout - fail gracefully if DB unreachable
63
+ cwd: opcDir,
64
+ env: {
65
+ ...process.env,
66
+ CONTINUOUS_CLAUDE_DB_URL: getPgConnectionString()
67
+ }
68
+ });
69
+ return {
70
+ success: result.status === 0,
71
+ stdout: result.stdout?.trim() || "",
72
+ stderr: result.stderr || ""
73
+ };
74
+ } catch (err) {
75
+ return {
76
+ success: false,
77
+ stdout: "",
78
+ stderr: String(err)
79
+ };
80
+ }
81
+ }
82
+ function registerSession(sessionId, project, workingOn = "") {
83
+ const pythonCode = `
84
+ import asyncpg
85
+ import os
86
+ from datetime import datetime
87
+
88
+ session_id = sys.argv[1]
89
+ project = sys.argv[2]
90
+ working_on = sys.argv[3] if len(sys.argv) > 3 else ''
91
+ pg_url = os.environ.get('CONTINUOUS_CLAUDE_DB_URL') or os.environ.get('DATABASE_URL', 'postgresql://claude:claude_dev@localhost:5432/continuous_claude')
92
+
93
+ async def main():
94
+ conn = await asyncpg.connect(pg_url)
95
+ try:
96
+ # Create table if not exists
97
+ await conn.execute('''
98
+ CREATE TABLE IF NOT EXISTS sessions (
99
+ id TEXT PRIMARY KEY,
100
+ project TEXT NOT NULL,
101
+ working_on TEXT,
102
+ started_at TIMESTAMP DEFAULT NOW(),
103
+ last_heartbeat TIMESTAMP DEFAULT NOW()
104
+ )
105
+ ''')
106
+
107
+ # Upsert session
108
+ await conn.execute('''
109
+ INSERT INTO sessions (id, project, working_on, started_at, last_heartbeat)
110
+ VALUES ($1, $2, $3, NOW(), NOW())
111
+ ON CONFLICT (id) DO UPDATE SET
112
+ working_on = EXCLUDED.working_on,
113
+ last_heartbeat = NOW()
114
+ ''', session_id, project, working_on)
115
+
116
+ print('ok')
117
+ finally:
118
+ await conn.close()
119
+
120
+ asyncio.run(main())
121
+ `;
122
+ const result = runPgQuery(pythonCode, [sessionId, project, workingOn]);
123
+ if (!result.success || result.stdout !== "ok") {
124
+ return {
125
+ success: false,
126
+ error: result.stderr || result.stdout || "Unknown error"
127
+ };
128
+ }
129
+ return { success: true };
130
+ }
131
+ function getActiveSessions(project) {
132
+ const pythonCode = `
133
+ import asyncpg
134
+ import os
135
+ import json
136
+ from datetime import datetime, timedelta
137
+
138
+ project_filter = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] != 'null' else None
139
+ pg_url = os.environ.get('CONTINUOUS_CLAUDE_DB_URL') or os.environ.get('DATABASE_URL', 'postgresql://claude:claude_dev@localhost:5432/continuous_claude')
140
+
141
+ async def main():
142
+ conn = await asyncpg.connect(pg_url)
143
+ try:
144
+ # Get sessions active in last 5 minutes
145
+ cutoff = datetime.utcnow() - timedelta(minutes=5)
146
+
147
+ if project_filter:
148
+ rows = await conn.fetch('''
149
+ SELECT id, project, working_on, started_at, last_heartbeat
150
+ FROM sessions
151
+ WHERE project = $1 AND last_heartbeat > $2
152
+ ORDER BY started_at DESC
153
+ ''', project_filter, cutoff)
154
+ else:
155
+ rows = await conn.fetch('''
156
+ SELECT id, project, working_on, started_at, last_heartbeat
157
+ FROM sessions
158
+ WHERE last_heartbeat > $1
159
+ ORDER BY started_at DESC
160
+ ''', cutoff)
161
+
162
+ sessions = []
163
+ for row in rows:
164
+ sessions.append({
165
+ 'id': row['id'],
166
+ 'project': row['project'],
167
+ 'working_on': row['working_on'],
168
+ 'started_at': row['started_at'].isoformat() if row['started_at'] else None,
169
+ 'last_heartbeat': row['last_heartbeat'].isoformat() if row['last_heartbeat'] else None
170
+ })
171
+
172
+ print(json.dumps(sessions))
173
+ except Exception as e:
174
+ print(json.dumps([]))
175
+ finally:
176
+ await conn.close()
177
+
178
+ asyncio.run(main())
179
+ `;
180
+ const result = runPgQuery(pythonCode, [project || "null"]);
181
+ if (!result.success) {
182
+ return { success: false, sessions: [] };
183
+ }
184
+ try {
185
+ const sessions = JSON.parse(result.stdout || "[]");
186
+ return { success: true, sessions };
187
+ } catch {
188
+ return { success: false, sessions: [] };
189
+ }
190
+ }
191
+
192
+ // src/shared/session-id.ts
193
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
194
+ import { join as join2 } from "path";
195
+ var SESSION_ID_FILENAME = ".coordination-session-id";
196
+ function getSessionIdFile(options = {}) {
197
+ const claudeDir = join2(process.env.HOME || "/tmp", ".claude");
198
+ if (options.createDir) {
199
+ try {
200
+ mkdirSync(claudeDir, { recursive: true, mode: 448 });
201
+ } catch {
202
+ }
203
+ }
204
+ return join2(claudeDir, SESSION_ID_FILENAME);
205
+ }
206
+ function generateSessionId() {
207
+ const spanId = process.env.BRAINTRUST_SPAN_ID;
208
+ if (spanId) {
209
+ return spanId.slice(0, 8);
210
+ }
211
+ return `s-${Date.now().toString(36)}`;
212
+ }
213
+ function writeSessionId(sessionId) {
214
+ try {
215
+ const filePath = getSessionIdFile({ createDir: true });
216
+ writeFileSync(filePath, sessionId, { encoding: "utf-8", mode: 384 });
217
+ return true;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+ function getProject() {
223
+ return process.env.CLAUDE_PROJECT_DIR || process.cwd();
224
+ }
225
+
226
+ // src/shared/context-budget.ts
227
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, statSync } from "fs";
228
+ import { join as join3 } from "path";
229
+ import { homedir } from "os";
230
+ var BUDGET_PATH = join3(homedir(), ".claude", "cache", "context-budget.json");
231
+ function saveBudget(budget) {
232
+ try {
233
+ const cacheDir = join3(homedir(), ".claude", "cache");
234
+ if (!existsSync2(cacheDir)) mkdirSync2(cacheDir, { recursive: true });
235
+ writeFileSync2(BUDGET_PATH, JSON.stringify(budget, null, 2));
236
+ } catch {
237
+ }
238
+ }
239
+ function resetBudget(sessionId) {
240
+ const budget = {
241
+ session_id: sessionId,
242
+ total_chars: 0,
243
+ per_hook: {},
244
+ per_event: {},
245
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
246
+ };
247
+ saveBudget(budget);
248
+ }
249
+
250
+ // src/session-register.ts
251
+ function main() {
252
+ let input;
253
+ try {
254
+ const stdinContent = readFileSync3(0, "utf-8");
255
+ input = JSON.parse(stdinContent);
256
+ } catch {
257
+ console.log(JSON.stringify({ result: "continue" }));
258
+ return;
259
+ }
260
+ const sessionId = generateSessionId();
261
+ const project = getProject();
262
+ const projectName = project.split("/").pop() || "unknown";
263
+ process.env.COORDINATION_SESSION_ID = sessionId;
264
+ try {
265
+ resetBudget(sessionId);
266
+ } catch {
267
+ }
268
+ if (!writeSessionId(sessionId)) {
269
+ console.error(`[session-register] WARNING: Failed to persist session ID ${sessionId} to file`);
270
+ }
271
+ const registerResult = registerSession(sessionId, project, "");
272
+ const sessionsResult = getActiveSessions(project);
273
+ const otherSessions = sessionsResult.sessions.filter((s) => s.id !== sessionId);
274
+ let awarenessMessage = `
275
+ <system-reminder>
276
+ MULTI-SESSION COORDINATION ACTIVE
277
+
278
+ Session: ${sessionId}
279
+ Project: ${projectName}
280
+ `;
281
+ if (otherSessions.length > 0) {
282
+ awarenessMessage += `
283
+ Active peer sessions (${otherSessions.length}):
284
+ ${otherSessions.map((s) => ` - ${s.id}: ${s.working_on || "working..."}`).join("\n")}
285
+
286
+ Coordination features:
287
+ - File edits are tracked to prevent conflicts
288
+ - Research findings are shared automatically
289
+ - Use Task tool normally - coordination happens via hooks
290
+ `;
291
+ } else {
292
+ awarenessMessage += `
293
+ No other sessions active on this project.
294
+ You are the only session currently working here.
295
+ `;
296
+ }
297
+ awarenessMessage += `</system-reminder>`;
298
+ const output = {
299
+ result: "continue",
300
+ message: awarenessMessage
301
+ };
302
+ console.log(JSON.stringify(output));
303
+ }
304
+ main();
305
+ export {
306
+ main
307
+ };