slashdev 1.3.0 → 1.3.1

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 (40) hide show
  1. package/hooks/post-response.sh +62 -1
  2. package/hooks/prompt-submit.sh +42 -0
  3. package/package.json +1 -1
  4. package/src/commands/setup-internals.js +22 -0
  5. package/vendor/gstack/.env.example +0 -5
  6. package/vendor/gstack/autoplan/SKILL.md +0 -1116
  7. package/vendor/gstack/browse/SKILL.md +0 -538
  8. package/vendor/gstack/canary/SKILL.md +0 -587
  9. package/vendor/gstack/careful/SKILL.md +0 -59
  10. package/vendor/gstack/codex/SKILL.md +0 -862
  11. package/vendor/gstack/connect-chrome/SKILL.md +0 -549
  12. package/vendor/gstack/cso/ACKNOWLEDGEMENTS.md +0 -14
  13. package/vendor/gstack/cso/SKILL.md +0 -929
  14. package/vendor/gstack/design-consultation/SKILL.md +0 -962
  15. package/vendor/gstack/design-review/SKILL.md +0 -1314
  16. package/vendor/gstack/design-shotgun/SKILL.md +0 -730
  17. package/vendor/gstack/document-release/SKILL.md +0 -718
  18. package/vendor/gstack/freeze/SKILL.md +0 -82
  19. package/vendor/gstack/gstack-upgrade/SKILL.md +0 -232
  20. package/vendor/gstack/guard/SKILL.md +0 -82
  21. package/vendor/gstack/investigate/SKILL.md +0 -504
  22. package/vendor/gstack/land-and-deploy/SKILL.md +0 -1367
  23. package/vendor/gstack/office-hours/SKILL.md +0 -1317
  24. package/vendor/gstack/plan-ceo-review/SKILL.md +0 -1537
  25. package/vendor/gstack/plan-design-review/SKILL.md +0 -1227
  26. package/vendor/gstack/plan-eng-review/SKILL.md +0 -1120
  27. package/vendor/gstack/qa/SKILL.md +0 -1136
  28. package/vendor/gstack/qa/references/issue-taxonomy.md +0 -85
  29. package/vendor/gstack/qa/templates/qa-report-template.md +0 -126
  30. package/vendor/gstack/qa-only/SKILL.md +0 -726
  31. package/vendor/gstack/retro/SKILL.md +0 -1197
  32. package/vendor/gstack/review/SKILL.md +0 -1138
  33. package/vendor/gstack/review/TODOS-format.md +0 -62
  34. package/vendor/gstack/review/checklist.md +0 -220
  35. package/vendor/gstack/review/design-checklist.md +0 -132
  36. package/vendor/gstack/review/greptile-triage.md +0 -220
  37. package/vendor/gstack/setup-browser-cookies/SKILL.md +0 -348
  38. package/vendor/gstack/setup-deploy/SKILL.md +0 -528
  39. package/vendor/gstack/ship/SKILL.md +0 -1931
  40. package/vendor/gstack/unfreeze/SKILL.md +0 -40
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const https = require('https');
5
5
  const path = require('path');
6
+ const { execFileSync } = require('child_process');
6
7
 
7
8
  // Debug log
8
9
  const DEBUG_LOG = path.join(require('os').homedir(), '.slashdev', 'hook-debug.log');
@@ -181,6 +182,54 @@ try {
181
182
  debug(`Conversation type derivation error: ${e.message}`);
182
183
  }
183
184
 
185
+ // v2: was this typed by a person? Claude Code also records background-task
186
+ // notifications, messages from other sessions, skill loads and IDE
187
+ // context as user turns; headless runs (claude -p) are scripts.
188
+ let source = 'human';
189
+ try {
190
+ const head = lastPrompt.trimStart().slice(0, 80).toLowerCase();
191
+ const entrypoint = String(process.env.CLAUDE_CODE_ENTRYPOINT || '');
192
+ if (!lastPrompt.trim()) source = 'unknown';
193
+ else if (entrypoint.startsWith('sdk')) source = 'scripted';
194
+ else if (head.startsWith('<task-notification>')) source = 'task_notification';
195
+ else if (head.startsWith('another claude session sent')) source = 'session_message';
196
+ else if (head.startsWith('base directory for this skill')) source = 'skill';
197
+ else if (head.startsWith('<ide_') || head.startsWith('<system-reminder>')) source = 'ide_context';
198
+ } catch (e) {
199
+ debug(`Source classification error: ${e.message}`);
200
+ }
201
+
202
+ // v2: when the prompt was sent (stamped by prompt-submit.sh).
203
+ let promptSubmittedAt = null;
204
+ try {
205
+ const safeId = String(sessionId).replace(/[^A-Za-z0-9_-]/g, '');
206
+ const stamp = path.join(require('os').homedir(), '.slashdev', 'sessions', `${safeId}.json`);
207
+ if (safeId && fs.existsSync(stamp)) {
208
+ promptSubmittedAt = JSON.parse(fs.readFileSync(stamp, 'utf8')).submitted_at || null;
209
+ }
210
+ } catch (e) {
211
+ debug(`Prompt stamp read error: ${e.message}`);
212
+ }
213
+
214
+ // v2: repo, branch and commit, to tie a session to what it shipped.
215
+ // The remote URL is reduced to owner/repo so no credentials leave.
216
+ let repo = null;
217
+ let branch = null;
218
+ let commitSha = null;
219
+ try {
220
+ const cwd = hookData.cwd;
221
+ if (cwd && fs.existsSync(cwd)) {
222
+ const git = (args) => execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8', timeout: 1500, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
223
+ branch = git(['rev-parse', '--abbrev-ref', 'HEAD']) || null;
224
+ commitSha = git(['rev-parse', 'HEAD']) || null;
225
+ const remote = git(['config', '--get', 'remote.origin.url']);
226
+ const m = remote.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?\/?$/);
227
+ repo = m ? m[1] : null;
228
+ }
229
+ } catch (e) {
230
+ // Not a git repo, or git missing: leave them null.
231
+ }
232
+
184
233
  const payload = JSON.stringify({
185
234
  engineer,
186
235
  session_id: sessionId,
@@ -192,9 +241,21 @@ try {
192
241
  conversation_type: conversationType,
193
242
  model: model,
194
243
  message_count: messageCount,
244
+ source,
245
+ prompt_submitted_at: promptSubmittedAt,
246
+ repo,
247
+ branch,
248
+ commit_sha: commitSha,
249
+ cli_version: config.packageVersion || null,
195
250
  });
196
251
 
197
- debug(`Sending: engineer=${engineer}, session=${sessionId.substring(0, 12)}, prompt_len=${lastPrompt.length}, response_len=${lastResponse.length}, tools=[${toolsUsed.join(',')}], skills=[${skillsUsed.join(',')}], project=${projectName}, type=${conversationType}, model=${model}, messages=${messageCount}`);
252
+ debug(`Sending: engineer=${engineer}, session=${sessionId.substring(0, 12)}, prompt_len=${lastPrompt.length}, response_len=${lastResponse.length}, source=${source}, submitted=${promptSubmittedAt}, repo=${repo}, branch=${branch}, tools=[${toolsUsed.join(',')}], skills=[${skillsUsed.join(',')}], project=${projectName}, type=${conversationType}, model=${model}, messages=${messageCount}`);
253
+
254
+ // Dry run (testing): log the payload, send nothing.
255
+ if (process.env.SLASHDEV_LOG_DRY === '1') {
256
+ debug(`DRY payload: ${payload}`);
257
+ process.exit(0);
258
+ }
198
259
 
199
260
  // POST to API
200
261
  const url = new URL('https://v1api.slashdev.io/api/logs');
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Claude Code UserPromptSubmit hook: note when a prompt was sent, so the Stop
4
+ // hook (post-response.sh) can log the time a person asked as well as the time
5
+ // Claude finished. Local only — no network, no output — and it never blocks
6
+ // a prompt: any error just exits 0.
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+
12
+ try {
13
+ let input = '';
14
+ process.stdin.setEncoding('utf8');
15
+ process.stdin.on('data', (chunk) => { input += chunk; });
16
+ process.stdin.on('end', () => {
17
+ try {
18
+ const hookData = input ? JSON.parse(input) : {};
19
+ const sessionId = String(hookData.session_id || '').replace(/[^A-Za-z0-9_-]/g, '');
20
+ if (!sessionId) process.exit(0);
21
+
22
+ const dir = path.join(os.homedir(), '.slashdev', 'sessions');
23
+ fs.mkdirSync(dir, { recursive: true });
24
+ fs.writeFileSync(
25
+ path.join(dir, `${sessionId}.json`),
26
+ JSON.stringify({ submitted_at: new Date().toISOString() })
27
+ );
28
+
29
+ // Housekeeping: drop stamps older than two days.
30
+ const cutoff = Date.now() - 2 * 24 * 60 * 60 * 1000;
31
+ for (const name of fs.readdirSync(dir)) {
32
+ try {
33
+ const file = path.join(dir, name);
34
+ if (fs.statSync(file).mtimeMs < cutoff) fs.unlinkSync(file);
35
+ } catch (_) { /* ignore */ }
36
+ }
37
+ } catch (_) { /* never block a prompt */ }
38
+ process.exit(0);
39
+ });
40
+ } catch (_) {
41
+ process.exit(0);
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slashdev",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Skills & workflows for AI agents in the Slashdev ecosystem",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -119,6 +119,28 @@ export function installHooks() {
119
119
  settings.hooks.Stop = stopHooks;
120
120
  }
121
121
 
122
+ // Register UserPromptSubmit hook: stamps when each prompt was sent, so the
123
+ // Stop hook can log it (local only, never blocks a prompt).
124
+ const submitCommand = join(CLAUDE_HOOKS_DIR, 'prompt-submit.sh');
125
+ if (existsSync(submitCommand)) {
126
+ const submitHooks = settings.hooks.UserPromptSubmit || [];
127
+ const submitRegistered = submitHooks.some(
128
+ (entry) => Array.isArray(entry.hooks) && entry.hooks.some((h) => h.command && h.command.includes('prompt-submit.sh'))
129
+ );
130
+
131
+ if (!submitRegistered) {
132
+ submitHooks.push({
133
+ hooks: [
134
+ {
135
+ type: 'command',
136
+ command: submitCommand,
137
+ },
138
+ ],
139
+ });
140
+ settings.hooks.UserPromptSubmit = submitHooks;
141
+ }
142
+ }
143
+
122
144
  // Register PreToolUse hook for the install guard (Bash tool only).
123
145
  const guardCommand = join(CLAUDE_HOOKS_DIR, 'pre-install-guard.sh');
124
146
  if (existsSync(guardCommand)) {
@@ -1,5 +0,0 @@
1
- # Copy to .env and fill in values
2
- # bun auto-loads .env — no dotenv needed
3
-
4
- # Required for LLM-as-judge evals (bun run test:eval)
5
- ANTHROPIC_API_KEY=sk-ant-your-key-here