gsd-code-first 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/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +937 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/gsd-advisor-researcher.md +104 -0
- package/agents/gsd-annotator.md +148 -0
- package/agents/gsd-arc-executor.md +537 -0
- package/agents/gsd-arc-planner.md +374 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-planner.md +155 -0
- package/agents/gsd-codebase-mapper.md +770 -0
- package/agents/gsd-debugger.md +1373 -0
- package/agents/gsd-executor.md +509 -0
- package/agents/gsd-integration-checker.md +443 -0
- package/agents/gsd-nyquist-auditor.md +176 -0
- package/agents/gsd-phase-researcher.md +698 -0
- package/agents/gsd-plan-checker.md +773 -0
- package/agents/gsd-planner.md +1354 -0
- package/agents/gsd-project-researcher.md +654 -0
- package/agents/gsd-prototyper.md +161 -0
- package/agents/gsd-research-synthesizer.md +247 -0
- package/agents/gsd-roadmapper.md +679 -0
- package/agents/gsd-ui-auditor.md +439 -0
- package/agents/gsd-ui-checker.md +300 -0
- package/agents/gsd-ui-researcher.md +357 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +700 -0
- package/bin/install.js +5009 -0
- package/commands/gsd/add-backlog.md +76 -0
- package/commands/gsd/add-phase.md +43 -0
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/add-todo.md +47 -0
- package/commands/gsd/annotate.md +54 -0
- package/commands/gsd/audit-milestone.md +36 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +41 -0
- package/commands/gsd/check-todos.md +45 -0
- package/commands/gsd/cleanup.md +18 -0
- package/commands/gsd/complete-milestone.md +136 -0
- package/commands/gsd/debug.md +173 -0
- package/commands/gsd/deep-plan.md +52 -0
- package/commands/gsd/discuss-phase.md +64 -0
- package/commands/gsd/do.md +30 -0
- package/commands/gsd/execute-phase.md +59 -0
- package/commands/gsd/extract-plan.md +35 -0
- package/commands/gsd/fast.md +30 -0
- package/commands/gsd/forensics.md +56 -0
- package/commands/gsd/health.md +22 -0
- package/commands/gsd/help.md +22 -0
- package/commands/gsd/insert-phase.md +32 -0
- package/commands/gsd/iterate.md +124 -0
- package/commands/gsd/join-discord.md +18 -0
- package/commands/gsd/list-phase-assumptions.md +46 -0
- package/commands/gsd/list-workspaces.md +19 -0
- package/commands/gsd/manager.md +39 -0
- package/commands/gsd/map-codebase.md +71 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/new-milestone.md +44 -0
- package/commands/gsd/new-project.md +42 -0
- package/commands/gsd/new-workspace.md +44 -0
- package/commands/gsd/next.md +24 -0
- package/commands/gsd/note.md +34 -0
- package/commands/gsd/pause-work.md +38 -0
- package/commands/gsd/plan-milestone-gaps.md +34 -0
- package/commands/gsd/plan-phase.md +47 -0
- package/commands/gsd/plant-seed.md +28 -0
- package/commands/gsd/pr-branch.md +25 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +24 -0
- package/commands/gsd/prototype.md +56 -0
- package/commands/gsd/quick.md +47 -0
- package/commands/gsd/reapply-patches.md +123 -0
- package/commands/gsd/remove-phase.md +31 -0
- package/commands/gsd/remove-workspace.md +26 -0
- package/commands/gsd/research-phase.md +195 -0
- package/commands/gsd/resume-work.md +40 -0
- package/commands/gsd/review-backlog.md +61 -0
- package/commands/gsd/review.md +37 -0
- package/commands/gsd/session-report.md +19 -0
- package/commands/gsd/set-mode.md +41 -0
- package/commands/gsd/set-profile.md +12 -0
- package/commands/gsd/settings.md +36 -0
- package/commands/gsd/ship.md +23 -0
- package/commands/gsd/stats.md +18 -0
- package/commands/gsd/thread.md +127 -0
- package/commands/gsd/ui-phase.md +34 -0
- package/commands/gsd/ui-review.md +32 -0
- package/commands/gsd/update.md +37 -0
- package/commands/gsd/validate-phase.md +35 -0
- package/commands/gsd/verify-work.md +38 -0
- package/commands/gsd/workstreams.md +63 -0
- package/get-shit-done/bin/gsd-tools.cjs +946 -0
- package/get-shit-done/bin/lib/arc-scanner.cjs +341 -0
- package/get-shit-done/bin/lib/commands.cjs +959 -0
- package/get-shit-done/bin/lib/config.cjs +466 -0
- package/get-shit-done/bin/lib/core.cjs +1230 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +336 -0
- package/get-shit-done/bin/lib/init.cjs +1442 -0
- package/get-shit-done/bin/lib/milestone.cjs +252 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +68 -0
- package/get-shit-done/bin/lib/phase.cjs +888 -0
- package/get-shit-done/bin/lib/profile-output.cjs +952 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +329 -0
- package/get-shit-done/bin/lib/security.cjs +382 -0
- package/get-shit-done/bin/lib/state.cjs +1031 -0
- package/get-shit-done/bin/lib/template.cjs +222 -0
- package/get-shit-done/bin/lib/uat.cjs +282 -0
- package/get-shit-done/bin/lib/verify.cjs +888 -0
- package/get-shit-done/bin/lib/workstream.cjs +491 -0
- package/get-shit-done/commands/gsd/workstreams.md +63 -0
- package/get-shit-done/references/arc-standard.md +315 -0
- package/get-shit-done/references/checkpoints.md +778 -0
- package/get-shit-done/references/continuation-format.md +249 -0
- package/get-shit-done/references/decimal-phase-calculation.md +64 -0
- package/get-shit-done/references/git-integration.md +295 -0
- package/get-shit-done/references/git-planning-commit.md +38 -0
- package/get-shit-done/references/model-profile-resolution.md +36 -0
- package/get-shit-done/references/model-profiles.md +139 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planning-config.md +202 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/tdd.md +263 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/references/workstream-flag.md +58 -0
- package/get-shit-done/templates/DEBUG.md +164 -0
- package/get-shit-done/templates/UAT.md +265 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/claude-md.md +122 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +44 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +186 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +552 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/state.md +176 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +351 -0
- package/get-shit-done/workflows/add-todo.md +158 -0
- package/get-shit-done/workflows/audit-milestone.md +340 -0
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +891 -0
- package/get-shit-done/workflows/check-todos.md +177 -0
- package/get-shit-done/workflows/cleanup.md +152 -0
- package/get-shit-done/workflows/complete-milestone.md +767 -0
- package/get-shit-done/workflows/diagnose-issues.md +231 -0
- package/get-shit-done/workflows/discovery-phase.md +289 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +653 -0
- package/get-shit-done/workflows/discuss-phase.md +1049 -0
- package/get-shit-done/workflows/do.md +104 -0
- package/get-shit-done/workflows/execute-phase.md +846 -0
- package/get-shit-done/workflows/execute-plan.md +514 -0
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/health.md +181 -0
- package/get-shit-done/workflows/help.md +634 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +362 -0
- package/get-shit-done/workflows/map-codebase.md +377 -0
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +486 -0
- package/get-shit-done/workflows/new-project.md +1250 -0
- package/get-shit-done/workflows/new-workspace.md +237 -0
- package/get-shit-done/workflows/next.md +97 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +156 -0
- package/get-shit-done/workflows/pause-work.md +176 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
- package/get-shit-done/workflows/plan-phase.md +859 -0
- package/get-shit-done/workflows/plant-seed.md +169 -0
- package/get-shit-done/workflows/pr-branch.md +129 -0
- package/get-shit-done/workflows/profile-user.md +450 -0
- package/get-shit-done/workflows/progress.md +507 -0
- package/get-shit-done/workflows/quick.md +757 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/remove-workspace.md +90 -0
- package/get-shit-done/workflows/research-phase.md +82 -0
- package/get-shit-done/workflows/resume-project.md +326 -0
- package/get-shit-done/workflows/review.md +228 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings.md +283 -0
- package/get-shit-done/workflows/ship.md +228 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/transition.md +671 -0
- package/get-shit-done/workflows/ui-phase.md +302 -0
- package/get-shit-done/workflows/ui-review.md +165 -0
- package/get-shit-done/workflows/update.md +323 -0
- package/get-shit-done/workflows/validate-phase.md +174 -0
- package/get-shit-done/workflows/verify-phase.md +254 -0
- package/get-shit-done/workflows/verify-work.md +637 -0
- package/hooks/dist/gsd-check-update.js +114 -0
- package/hooks/dist/gsd-context-monitor.js +156 -0
- package/hooks/dist/gsd-prompt-guard.js +96 -0
- package/hooks/dist/gsd-statusline.js +119 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/package.json +52 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +82 -0
- package/scripts/prompt-injection-scan.sh +198 -0
- package/scripts/run-tests.cjs +29 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Pipeline — session scanning, message extraction, and sampling
|
|
3
|
+
*
|
|
4
|
+
* Reads Claude Code session history (read-only) to extract user messages
|
|
5
|
+
* for behavioral profiling. Three commands:
|
|
6
|
+
* - scan-sessions: list all projects and sessions
|
|
7
|
+
* - extract-messages: extract user messages from a specific project
|
|
8
|
+
* - profile-sample: multi-project sampling with recency weighting
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const readline = require('readline');
|
|
15
|
+
const { output, error, safeReadFile, reapStaleTempFiles } = require('./core.cjs');
|
|
16
|
+
|
|
17
|
+
// ─── Session I/O Helpers ──────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
function getSessionsDir(overridePath) {
|
|
20
|
+
const dir = overridePath || path.join(os.homedir(), '.claude', 'projects');
|
|
21
|
+
if (!fs.existsSync(dir)) return null;
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function scanProjectDir(projectDirPath) {
|
|
26
|
+
const entries = fs.readdirSync(projectDirPath);
|
|
27
|
+
const sessions = [];
|
|
28
|
+
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
31
|
+
const sessionId = entry.replace('.jsonl', '');
|
|
32
|
+
const filePath = path.join(projectDirPath, entry);
|
|
33
|
+
const stat = fs.statSync(filePath);
|
|
34
|
+
|
|
35
|
+
sessions.push({
|
|
36
|
+
sessionId,
|
|
37
|
+
filePath,
|
|
38
|
+
size: stat.size,
|
|
39
|
+
modified: stat.mtime,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
sessions.sort((a, b) => b.modified - a.modified);
|
|
44
|
+
return sessions;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readSessionIndex(projectDirPath) {
|
|
48
|
+
try {
|
|
49
|
+
const indexPath = path.join(projectDirPath, 'sessions-index.json');
|
|
50
|
+
const raw = fs.readFileSync(indexPath, 'utf-8');
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
const entries = new Map();
|
|
53
|
+
for (const entry of (parsed.entries || [])) {
|
|
54
|
+
if (entry.sessionId) {
|
|
55
|
+
entries.set(entry.sessionId, entry);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { originalPath: parsed.originalPath || null, entries };
|
|
59
|
+
} catch {
|
|
60
|
+
return { originalPath: null, entries: new Map() };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getProjectName(projectDirName, indexData, firstRecordCwd) {
|
|
65
|
+
if (indexData && indexData.originalPath) {
|
|
66
|
+
return path.basename(indexData.originalPath);
|
|
67
|
+
}
|
|
68
|
+
if (firstRecordCwd) {
|
|
69
|
+
return path.basename(firstRecordCwd);
|
|
70
|
+
}
|
|
71
|
+
return projectDirName;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function formatBytes(bytes) {
|
|
75
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
76
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
77
|
+
if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
78
|
+
return `${(bytes / 1073741824).toFixed(1)} GB`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatProjectTable(projects) {
|
|
82
|
+
let out = '';
|
|
83
|
+
out += 'Project'.padEnd(35) + 'Sessions'.padEnd(10) + 'Size'.padEnd(10) + 'Last Active\n';
|
|
84
|
+
out += '-'.repeat(75) + '\n';
|
|
85
|
+
for (const p of projects) {
|
|
86
|
+
const name = p.name.length > 33 ? p.name.substring(0, 30) + '...' : p.name;
|
|
87
|
+
out += name.padEnd(35) + String(p.sessionCount).padEnd(10) +
|
|
88
|
+
p.totalSizeHuman.padEnd(10) + p.lastActive + '\n';
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatSessionTable(sessions) {
|
|
94
|
+
let out = '';
|
|
95
|
+
out += ' Session ID'.padEnd(42) + 'Size'.padEnd(10) + 'Modified\n';
|
|
96
|
+
out += ' ' + '-'.repeat(70) + '\n';
|
|
97
|
+
for (const s of sessions) {
|
|
98
|
+
const id = s.sessionId.length > 38 ? s.sessionId.substring(0, 35) + '...' : s.sessionId;
|
|
99
|
+
out += ' ' + id.padEnd(40) + formatBytes(s.size).padEnd(10) +
|
|
100
|
+
new Date(s.modified).toISOString().replace('T', ' ').substring(0, 19) + '\n';
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Message Extraction Helpers ───────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function isGenuineUserMessage(record) {
|
|
108
|
+
if (record.type !== 'user') return false;
|
|
109
|
+
if (record.userType !== 'external') return false;
|
|
110
|
+
if (record.isMeta === true) return false;
|
|
111
|
+
if (record.isSidechain === true) return false;
|
|
112
|
+
const content = record.message?.content;
|
|
113
|
+
if (typeof content !== 'string') return false;
|
|
114
|
+
if (content.length === 0) return false;
|
|
115
|
+
if (content.startsWith('<local-command')) return false;
|
|
116
|
+
if (content.startsWith('<command-')) return false;
|
|
117
|
+
if (content.startsWith('<task-notification')) return false;
|
|
118
|
+
if (content.startsWith('<local-command-stdout')) return false;
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function truncateContent(content, maxLen = 2000) {
|
|
123
|
+
if (content.length <= maxLen) return content;
|
|
124
|
+
return content.substring(0, maxLen) + '... [truncated]';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function streamExtractMessages(filePath, filterFn, maxMessages = 300) {
|
|
128
|
+
const rl = readline.createInterface({
|
|
129
|
+
input: fs.createReadStream(filePath),
|
|
130
|
+
crlfDelay: Infinity,
|
|
131
|
+
terminal: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const messages = [];
|
|
135
|
+
const sessionId = path.basename(filePath, '.jsonl');
|
|
136
|
+
|
|
137
|
+
for await (const line of rl) {
|
|
138
|
+
if (messages.length >= maxMessages) break;
|
|
139
|
+
let record;
|
|
140
|
+
try {
|
|
141
|
+
record = JSON.parse(line);
|
|
142
|
+
} catch {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!filterFn(record)) continue;
|
|
146
|
+
messages.push({
|
|
147
|
+
sessionId,
|
|
148
|
+
projectPath: record.cwd || null,
|
|
149
|
+
timestamp: record.timestamp || null,
|
|
150
|
+
content: truncateContent(record.message.content),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return messages;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
async function cmdScanSessions(overridePath, options, raw) {
|
|
160
|
+
const sessionsDir = getSessionsDir(overridePath);
|
|
161
|
+
if (!sessionsDir) {
|
|
162
|
+
const searchedPath = overridePath || '~/.claude/projects';
|
|
163
|
+
error(`No Claude Code sessions found at ${searchedPath}.${overridePath ? '' : ' Is Claude Code installed?'}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
process.stderr.write('Reading your session history (read-only, nothing is modified or sent anywhere)...\n');
|
|
167
|
+
|
|
168
|
+
let projectDirs;
|
|
169
|
+
try {
|
|
170
|
+
projectDirs = fs.readdirSync(sessionsDir).filter(entry => {
|
|
171
|
+
const fullPath = path.join(sessionsDir, entry);
|
|
172
|
+
try {
|
|
173
|
+
return fs.statSync(fullPath).isDirectory();
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
error(`Cannot read sessions directory: ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const projects = [];
|
|
183
|
+
|
|
184
|
+
for (const dirName of projectDirs) {
|
|
185
|
+
const projectPath = path.join(sessionsDir, dirName);
|
|
186
|
+
const sessions = scanProjectDir(projectPath);
|
|
187
|
+
if (sessions.length === 0) continue;
|
|
188
|
+
|
|
189
|
+
const indexData = readSessionIndex(projectPath);
|
|
190
|
+
const projectName = getProjectName(dirName, indexData);
|
|
191
|
+
|
|
192
|
+
if (indexData.entries.size === 0 && !options.json) {
|
|
193
|
+
process.stderr.write(`Index not found for ${projectName}, scanning directory...\n`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const totalSize = sessions.reduce((sum, s) => sum + s.size, 0);
|
|
197
|
+
const lastActive = sessions[0].modified.toISOString();
|
|
198
|
+
const oldest = sessions[sessions.length - 1].modified.toISOString();
|
|
199
|
+
const newest = sessions[0].modified.toISOString();
|
|
200
|
+
|
|
201
|
+
const project = {
|
|
202
|
+
name: projectName,
|
|
203
|
+
directory: dirName,
|
|
204
|
+
sessionCount: sessions.length,
|
|
205
|
+
totalSize,
|
|
206
|
+
totalSizeHuman: formatBytes(totalSize),
|
|
207
|
+
lastActive: lastActive.replace('T', ' ').substring(0, 19),
|
|
208
|
+
dateRange: { first: oldest, last: newest },
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (options.verbose) {
|
|
212
|
+
project.sessions = sessions.map(s => {
|
|
213
|
+
const indexed = indexData.entries.get(s.sessionId);
|
|
214
|
+
const session = {
|
|
215
|
+
sessionId: s.sessionId,
|
|
216
|
+
size: s.size,
|
|
217
|
+
sizeHuman: formatBytes(s.size),
|
|
218
|
+
modified: s.modified.toISOString(),
|
|
219
|
+
};
|
|
220
|
+
if (indexed) {
|
|
221
|
+
if (indexed.summary) session.summary = indexed.summary;
|
|
222
|
+
if (indexed.messageCount !== undefined) session.messageCount = indexed.messageCount;
|
|
223
|
+
if (indexed.created) session.created = indexed.created;
|
|
224
|
+
}
|
|
225
|
+
return session;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
projects.push(project);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
projects.sort((a, b) => b.dateRange.last.localeCompare(a.dateRange.last));
|
|
233
|
+
|
|
234
|
+
if (options.json || raw) {
|
|
235
|
+
output(projects, raw);
|
|
236
|
+
} else {
|
|
237
|
+
process.stdout.write('\n' + formatProjectTable(projects));
|
|
238
|
+
if (options.verbose) {
|
|
239
|
+
for (const p of projects) {
|
|
240
|
+
process.stdout.write(`\n ${p.name} (${p.sessionCount} sessions):\n`);
|
|
241
|
+
if (p.sessions) {
|
|
242
|
+
process.stdout.write(formatSessionTable(p.sessions));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(`\nTotal: ${projects.length} projects\n`);
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function cmdExtractMessages(projectArg, options, raw, overridePath) {
|
|
252
|
+
const sessionsDir = getSessionsDir(overridePath);
|
|
253
|
+
if (!sessionsDir) {
|
|
254
|
+
const searchedPath = overridePath || '~/.claude/projects';
|
|
255
|
+
error(`No Claude Code sessions found at ${searchedPath}.${overridePath ? '' : ' Is Claude Code installed?'}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let projectDirs;
|
|
259
|
+
try {
|
|
260
|
+
projectDirs = fs.readdirSync(sessionsDir).filter(entry => {
|
|
261
|
+
const fullPath = path.join(sessionsDir, entry);
|
|
262
|
+
try {
|
|
263
|
+
return fs.statSync(fullPath).isDirectory();
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
} catch (err) {
|
|
269
|
+
error(`Cannot read sessions directory: ${err.message}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let matchedDir = null;
|
|
273
|
+
let matchedName = null;
|
|
274
|
+
|
|
275
|
+
for (const dirName of projectDirs) {
|
|
276
|
+
if (dirName === projectArg) {
|
|
277
|
+
matchedDir = dirName;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!matchedDir) {
|
|
283
|
+
const lowerArg = projectArg.toLowerCase();
|
|
284
|
+
const matches = projectDirs.filter(d => d.toLowerCase().includes(lowerArg));
|
|
285
|
+
if (matches.length === 1) {
|
|
286
|
+
matchedDir = matches[0];
|
|
287
|
+
} else if (matches.length > 1) {
|
|
288
|
+
const exactNameMatches = [];
|
|
289
|
+
for (const dirName of matches) {
|
|
290
|
+
const indexData = readSessionIndex(path.join(sessionsDir, dirName));
|
|
291
|
+
const pName = getProjectName(dirName, indexData);
|
|
292
|
+
if (pName.toLowerCase() === lowerArg) {
|
|
293
|
+
exactNameMatches.push({ dirName, name: pName });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (exactNameMatches.length === 1) {
|
|
297
|
+
matchedDir = exactNameMatches[0].dirName;
|
|
298
|
+
matchedName = exactNameMatches[0].name;
|
|
299
|
+
} else {
|
|
300
|
+
const names = matches.map(d => {
|
|
301
|
+
const idx = readSessionIndex(path.join(sessionsDir, d));
|
|
302
|
+
return ` - ${getProjectName(d, idx)} (${d})`;
|
|
303
|
+
});
|
|
304
|
+
error(`Multiple projects match "${projectArg}":\n${names.join('\n')}\nBe more specific.`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!matchedDir) {
|
|
310
|
+
const available = projectDirs.map(d => {
|
|
311
|
+
const idx = readSessionIndex(path.join(sessionsDir, d));
|
|
312
|
+
return ` - ${getProjectName(d, idx)}`;
|
|
313
|
+
});
|
|
314
|
+
error(`No project matching "${projectArg}". Available projects:\n${available.join('\n')}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const projectPath = path.join(sessionsDir, matchedDir);
|
|
318
|
+
const indexData = readSessionIndex(projectPath);
|
|
319
|
+
const projectName = matchedName || getProjectName(matchedDir, indexData);
|
|
320
|
+
|
|
321
|
+
process.stderr.write('Reading your session history (read-only, nothing is modified or sent anywhere)...\n');
|
|
322
|
+
|
|
323
|
+
let sessions = scanProjectDir(projectPath);
|
|
324
|
+
|
|
325
|
+
if (options.sessionId) {
|
|
326
|
+
sessions = sessions.filter(s => s.sessionId === options.sessionId);
|
|
327
|
+
if (sessions.length === 0) {
|
|
328
|
+
error(`Session "${options.sessionId}" not found in project "${projectName}".`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (options.limit && options.limit > 0) {
|
|
333
|
+
sessions = sessions.slice(0, options.limit);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
reapStaleTempFiles('gsd-pipeline-', { dirsOnly: true });
|
|
337
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-pipeline-'));
|
|
338
|
+
const outputPath = path.join(tmpDir, 'extracted-messages.jsonl');
|
|
339
|
+
|
|
340
|
+
let sessionsProcessed = 0;
|
|
341
|
+
let sessionsSkipped = 0;
|
|
342
|
+
let messagesExtracted = 0;
|
|
343
|
+
let messagesTruncated = 0;
|
|
344
|
+
const total = sessions.length;
|
|
345
|
+
const batchLimit = 300;
|
|
346
|
+
|
|
347
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
348
|
+
if (messagesExtracted >= batchLimit) break;
|
|
349
|
+
|
|
350
|
+
const session = sessions[i];
|
|
351
|
+
process.stderr.write(`\rProcessing session ${i + 1}/${total}...`);
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const remaining = batchLimit - messagesExtracted;
|
|
355
|
+
const msgs = await streamExtractMessages(session.filePath, isGenuineUserMessage, remaining);
|
|
356
|
+
for (const msg of msgs) {
|
|
357
|
+
fs.appendFileSync(outputPath, JSON.stringify(msg) + '\n');
|
|
358
|
+
messagesExtracted++;
|
|
359
|
+
if (msg.content.endsWith('... [truncated]')) {
|
|
360
|
+
messagesTruncated++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
sessionsProcessed++;
|
|
364
|
+
} catch (err) {
|
|
365
|
+
sessionsSkipped++;
|
|
366
|
+
process.stderr.write(`\nWarning: Skipped session ${session.sessionId}: ${err.message}\n`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
process.stderr.write('\r' + ' '.repeat(60) + '\r');
|
|
371
|
+
|
|
372
|
+
const result = {
|
|
373
|
+
output_file: outputPath,
|
|
374
|
+
project: projectName,
|
|
375
|
+
sessions_processed: sessionsProcessed,
|
|
376
|
+
sessions_skipped: sessionsSkipped,
|
|
377
|
+
messages_extracted: messagesExtracted,
|
|
378
|
+
messages_truncated: messagesTruncated,
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
if (sessionsSkipped > 0 && sessionsProcessed > 0) {
|
|
382
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
383
|
+
process.exit(2);
|
|
384
|
+
} else if (sessionsProcessed === 0 && sessionsSkipped > 0) {
|
|
385
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
386
|
+
process.exit(1);
|
|
387
|
+
} else {
|
|
388
|
+
output(result, raw);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function cmdProfileSample(overridePath, options, raw) {
|
|
393
|
+
const sessionsDir = getSessionsDir(overridePath);
|
|
394
|
+
if (!sessionsDir) {
|
|
395
|
+
const searchedPath = overridePath || '~/.claude/projects';
|
|
396
|
+
error(`No Claude Code sessions found at ${searchedPath}.${overridePath ? '' : ' Is Claude Code installed?'}`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
process.stderr.write('Reading your session history (read-only, nothing is modified or sent anywhere)...\n');
|
|
400
|
+
|
|
401
|
+
const limit = options.limit || 150;
|
|
402
|
+
const maxChars = options.maxChars || 500;
|
|
403
|
+
|
|
404
|
+
let projectDirs;
|
|
405
|
+
try {
|
|
406
|
+
projectDirs = fs.readdirSync(sessionsDir).filter(entry => {
|
|
407
|
+
const fullPath = path.join(sessionsDir, entry);
|
|
408
|
+
try {
|
|
409
|
+
return fs.statSync(fullPath).isDirectory();
|
|
410
|
+
} catch {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
} catch (err) {
|
|
415
|
+
error(`Cannot read sessions directory: ${err.message}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (projectDirs.length === 0) {
|
|
419
|
+
error('No project directories found in sessions directory.');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const projectMeta = [];
|
|
423
|
+
for (const dirName of projectDirs) {
|
|
424
|
+
const projectPath = path.join(sessionsDir, dirName);
|
|
425
|
+
const sessions = scanProjectDir(projectPath);
|
|
426
|
+
if (sessions.length === 0) continue;
|
|
427
|
+
const indexData = readSessionIndex(projectPath);
|
|
428
|
+
const projectName = getProjectName(dirName, indexData);
|
|
429
|
+
const lastActive = sessions[0].modified;
|
|
430
|
+
projectMeta.push({ dirName, projectPath, sessions, projectName, lastActive });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
projectMeta.sort((a, b) => b.lastActive - a.lastActive);
|
|
434
|
+
|
|
435
|
+
const projectCount = projectMeta.length;
|
|
436
|
+
if (projectCount === 0) {
|
|
437
|
+
error('No projects with sessions found.');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const perProjectCap = options.maxPerProject || Math.max(5, Math.floor(limit / projectCount));
|
|
441
|
+
|
|
442
|
+
const recencyThreshold = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
|
443
|
+
const allMessages = [];
|
|
444
|
+
let skippedContextDumps = 0;
|
|
445
|
+
const projectBreakdown = [];
|
|
446
|
+
|
|
447
|
+
for (const proj of projectMeta) {
|
|
448
|
+
if (allMessages.length >= limit) break;
|
|
449
|
+
|
|
450
|
+
const cappedSessions = proj.sessions.slice(0, perProjectCap);
|
|
451
|
+
|
|
452
|
+
let projectMessages = 0;
|
|
453
|
+
let projectSessionsUsed = 0;
|
|
454
|
+
|
|
455
|
+
for (const session of cappedSessions) {
|
|
456
|
+
if (allMessages.length >= limit) break;
|
|
457
|
+
|
|
458
|
+
const isRecent = session.modified.getTime() >= recencyThreshold;
|
|
459
|
+
const perSessionMax = isRecent ? 10 : 3;
|
|
460
|
+
|
|
461
|
+
const remaining = Math.min(perSessionMax, limit - allMessages.length);
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const msgs = await streamExtractMessages(session.filePath, isGenuineUserMessage, remaining);
|
|
465
|
+
let sessionUsed = false;
|
|
466
|
+
|
|
467
|
+
for (const msg of msgs) {
|
|
468
|
+
if (allMessages.length >= limit) break;
|
|
469
|
+
|
|
470
|
+
const content = msg.content || '';
|
|
471
|
+
if (content.startsWith('This session is being continued')) {
|
|
472
|
+
skippedContextDumps++;
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const lines = content.split('\n').filter(l => l.trim().length > 0);
|
|
477
|
+
if (lines.length > 3) {
|
|
478
|
+
const logPattern = /^\[?(DEBUG|INFO|WARN|ERROR|LOG)\]?/i;
|
|
479
|
+
const timestampPattern = /^\d{4}-\d{2}-\d{2}/;
|
|
480
|
+
const logLines = lines.filter(l => logPattern.test(l.trim()) || timestampPattern.test(l.trim()));
|
|
481
|
+
if (logLines.length / lines.length > 0.8) {
|
|
482
|
+
skippedContextDumps++;
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const truncated = truncateContent(content, maxChars);
|
|
488
|
+
|
|
489
|
+
allMessages.push({
|
|
490
|
+
sessionId: msg.sessionId,
|
|
491
|
+
projectName: proj.projectName,
|
|
492
|
+
projectPath: msg.projectPath,
|
|
493
|
+
timestamp: msg.timestamp,
|
|
494
|
+
content: truncated,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
projectMessages++;
|
|
498
|
+
sessionUsed = true;
|
|
499
|
+
}
|
|
500
|
+
if (sessionUsed) projectSessionsUsed++;
|
|
501
|
+
} catch {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (projectMessages > 0) {
|
|
507
|
+
projectBreakdown.push({
|
|
508
|
+
project: proj.projectName,
|
|
509
|
+
messages: projectMessages,
|
|
510
|
+
sessions: projectSessionsUsed,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
reapStaleTempFiles('gsd-profile-', { dirsOnly: true });
|
|
516
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-profile-'));
|
|
517
|
+
const outputPath = path.join(tmpDir, 'profile-sample.jsonl');
|
|
518
|
+
for (const msg of allMessages) {
|
|
519
|
+
fs.appendFileSync(outputPath, JSON.stringify(msg) + '\n');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const result = {
|
|
523
|
+
output_file: outputPath,
|
|
524
|
+
projects_sampled: projectBreakdown.length,
|
|
525
|
+
messages_sampled: allMessages.length,
|
|
526
|
+
per_project_cap: perProjectCap,
|
|
527
|
+
message_char_limit: maxChars,
|
|
528
|
+
skipped_context_dumps: skippedContextDumps,
|
|
529
|
+
project_breakdown: projectBreakdown,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
output(result, raw);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
module.exports = {
|
|
536
|
+
cmdScanSessions,
|
|
537
|
+
cmdExtractMessages,
|
|
538
|
+
cmdProfileSample,
|
|
539
|
+
};
|