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.
- package/AGENTS.md +108 -0
- package/CLAUDE.md +171 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/agents/analysis-orchestrator.md +162 -0
- package/agents/audit-trail-builder.md +127 -0
- package/agents/category-developer.md +179 -0
- package/agents/citation-manager.md +83 -0
- package/agents/constant-comparator.md +135 -0
- package/agents/data-manager.md +104 -0
- package/agents/discussion-writer.md +128 -0
- package/agents/document-analyst.md +114 -0
- package/agents/ethics-reviewer.md +119 -0
- package/agents/field-note-analyst.md +124 -0
- package/agents/fit-assessor.md +192 -0
- package/agents/grounded-theorist.md +210 -0
- package/agents/literature-integrator.md +169 -0
- package/agents/literature-reviewer.md +112 -0
- package/agents/memo-writer.md +234 -0
- package/agents/methodology-critic.md +166 -0
- package/agents/methods-writer.md +109 -0
- package/agents/open-coder.md +187 -0
- package/agents/pattern-analyst.md +166 -0
- package/agents/peer-reviewer.md +129 -0
- package/agents/planner.md +122 -0
- package/agents/proposal-writer.md +108 -0
- package/agents/reflexivity-auditor.md +128 -0
- package/agents/research-designer.md +164 -0
- package/agents/research-writer.md +100 -0
- package/agents/saturation-assessor.md +159 -0
- package/agents/selective-coder.md +167 -0
- package/agents/theoretical-coder.md +260 -0
- package/agents/theoretical-sampler.md +165 -0
- package/agents/transcript-analyst.md +123 -0
- package/bin/cli.mjs +236 -0
- package/hooks/dist/agent-memory-loader.mjs +94 -0
- package/hooks/dist/agent-memory-saver.mjs +113 -0
- package/hooks/dist/bash-audit-log.mjs +71 -0
- package/hooks/dist/credential-deny.mjs +165 -0
- package/hooks/dist/forge-compile-check.mjs +92 -0
- package/hooks/dist/gas-snapshot-diff.mjs +71 -0
- package/hooks/dist/memory-awareness.mjs +276 -0
- package/hooks/dist/natspec-enforcer.mjs +67 -0
- package/hooks/dist/passive-learner.mjs +220 -0
- package/hooks/dist/pre-compact-continuity.mjs +467 -0
- package/hooks/dist/sast-on-edit.mjs +230 -0
- package/hooks/dist/session-analytics.mjs +84 -0
- package/hooks/dist/session-end-cleanup.mjs +121 -0
- package/hooks/dist/session-outcome.mjs +84 -0
- package/hooks/dist/session-register.mjs +307 -0
- package/hooks/dist/session-start-continuity.mjs +405 -0
- package/hooks/dist/slither-on-save.mjs +87 -0
- package/hooks/dist/storage-layout-check.mjs +89 -0
- package/hooks/dist/transcript-parser.mjs +214 -0
- package/install.sh +194 -0
- package/package.json +46 -0
- package/plugin.json +19 -0
- package/rules/academic-writing-style.md +42 -0
- package/rules/citation-standards.md +47 -0
- package/rules/current-methodological-state.md +40 -0
- package/rules/data-handling.md +44 -0
- package/rules/finding-output-format.md +47 -0
- package/rules/gt-coding-standards.md +40 -0
- package/rules/methodological-rigor.md +56 -0
- package/rules/quality-criteria.md +41 -0
- package/rules/reflexivity-requirements.md +40 -0
- package/rules/research-ethics-standards.md +44 -0
- package/skills/.gitkeep +2 -0
- package/skills/academic-writing/SKILL.md +73 -0
- package/skills/action-research/SKILL.md +96 -0
- package/skills/apa-formatting/SKILL.md +85 -0
- package/skills/case-study-methods/SKILL.md +96 -0
- package/skills/category-development/SKILL.md +80 -0
- package/skills/chicago-formatting/SKILL.md +81 -0
- package/skills/coding-pipeline/SKILL.md +81 -0
- package/skills/conceptual-frameworks/SKILL.md +70 -0
- package/skills/constant-comparison/SKILL.md +188 -0
- package/skills/constructivist-gt/SKILL.md +91 -0
- package/skills/data-management-protocols/SKILL.md +67 -0
- package/skills/document-analysis/SKILL.md +66 -0
- package/skills/ethnographic-methods/SKILL.md +82 -0
- package/skills/focus-group-methods/SKILL.md +66 -0
- package/skills/formal-theory/SKILL.md +159 -0
- package/skills/glaserian-grounded-theory/SKILL.md +212 -0
- package/skills/interview-design/SKILL.md +67 -0
- package/skills/literature-synthesis/SKILL.md +71 -0
- package/skills/member-checking/SKILL.md +66 -0
- package/skills/memo-writing/SKILL.md +158 -0
- package/skills/mixed-methods-design/SKILL.md +69 -0
- package/skills/narrative-inquiry/SKILL.md +101 -0
- package/skills/observation-methods/SKILL.md +67 -0
- package/skills/open-coding/SKILL.md +176 -0
- package/skills/paradigmatic-positioning/SKILL.md +72 -0
- package/skills/peer-debriefing/SKILL.md +72 -0
- package/skills/phenomenological-methods/SKILL.md +91 -0
- package/skills/qualitative-rigor/SKILL.md +78 -0
- package/skills/reflexive-practice/SKILL.md +64 -0
- package/skills/research-ethics/SKILL.md +64 -0
- package/skills/research-proposal-writing/SKILL.md +81 -0
- package/skills/research-questions/SKILL.md +66 -0
- package/skills/sampling-strategies/SKILL.md +61 -0
- package/skills/selective-coding/SKILL.md +183 -0
- package/skills/situational-analysis/SKILL.md +93 -0
- package/skills/substantive-theory/SKILL.md +169 -0
- package/skills/thematic-analysis/SKILL.md +80 -0
- package/skills/theoretical-coding/SKILL.md +213 -0
- package/skills/theoretical-sampling/SKILL.md +152 -0
- package/skills/theoretical-saturation/SKILL.md +179 -0
- package/skills/theoretical-sensitivity/SKILL.md +175 -0
- package/skills/theory-integration/SKILL.md +85 -0
- package/skills/thick-description/SKILL.md +69 -0
- package/skills/triangulation/SKILL.md +65 -0
- package/skills/visual-modeling/SKILL.md +66 -0
- 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
|
+
};
|