xtrm-tools 2.4.2 → 2.4.3

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/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtrm-cli",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -197,20 +197,25 @@ function main() {
197
197
  }
198
198
  }
199
199
 
200
- // Auto-clear: bd close <id> — remove the kv claim so commit gate unblocks
200
+ // On bd close: mark as closed-this-session for memory gate (don't clear claim yet)
201
+ // Memory gate will clear the claim after user acknowledges memory prompt
201
202
  if (/\bbd\s+close\b/.test(command) && commandSucceeded(input)) {
202
- const result = spawnSync('bd', ['kv', 'clear', `claimed:${sessionId}`], {
203
- cwd,
204
- stdio: ['pipe', 'pipe', 'pipe'],
205
- timeout: 5000,
206
- });
207
-
208
- if (result.status === 0) {
209
- process.stdout.write(JSON.stringify({
210
- additionalContext: `\nšŸ”“ **Beads**: Session claim cleared. Ready to commit.`,
211
- }));
212
- process.stdout.write('\n');
203
+ const match = command.match(/\bbd\s+close\s+(\S+)/);
204
+ const closedIssueId = match?.[1];
205
+
206
+ // Mark this issue as closed this session (memory gate reads this)
207
+ if (closedIssueId) {
208
+ spawnSync('bd', ['kv', 'set', `closed-this-session:${sessionId}`, closedIssueId], {
209
+ cwd,
210
+ stdio: ['pipe', 'pipe', 'pipe'],
211
+ timeout: 5000,
212
+ });
213
213
  }
214
+
215
+ process.stdout.write(JSON.stringify({
216
+ additionalContext: `\nšŸ”“ **Beads**: Issue closed. Memory gate will prompt on session end.`,
217
+ }));
218
+ process.stdout.write('\n');
214
219
  process.exit(0);
215
220
  }
216
221
 
@@ -11,7 +11,7 @@ import { execSync } from 'node:child_process';
11
11
  import { existsSync, unlinkSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
13
  import { readHookInput } from './beads-gate-core.mjs';
14
- import { resolveCwd, isBeadsProject, getSessionClaim } from './beads-gate-utils.mjs';
14
+ import { resolveCwd, isBeadsProject, getSessionClaim, clearSessionClaim } from './beads-gate-utils.mjs';
15
15
  import { memoryPromptMessage } from './beads-gate-messages.mjs';
16
16
 
17
17
  const input = readHookInput();
@@ -20,35 +20,43 @@ if (!input) process.exit(0);
20
20
  const cwd = resolveCwd(input);
21
21
  if (!cwd || !isBeadsProject(cwd)) process.exit(0);
22
22
 
23
+ const sessionId = input.session_id ?? null;
24
+ if (!sessionId) process.exit(0);
25
+
23
26
  // Agent signals evaluation complete by touching this marker, then stops again
24
27
  const marker = join(cwd, '.beads', '.memory-gate-done');
25
28
  if (existsSync(marker)) {
26
29
  try { unlinkSync(marker); } catch { /* ignore */ }
30
+ // Clear the claim and closed-this-session marker
31
+ clearSessionClaim(sessionId, cwd);
32
+ try {
33
+ execSync(`bd kv clear "closed-this-session:${sessionId}"`, {
34
+ cwd,
35
+ stdio: ['pipe', 'pipe', 'pipe'],
36
+ timeout: 5000,
37
+ });
38
+ } catch { /* ignore */ }
27
39
  process.exit(0);
28
40
  }
29
41
 
30
- // Only fire if this session had an active claim that is now closed
31
- const sessionId = input.session_id ?? null;
32
- if (!sessionId) process.exit(0);
33
-
34
- const claimId = getSessionClaim(sessionId, cwd);
35
- if (!claimId) process.exit(0); // no claim this session → no work to persist
36
-
37
- // Check if the claimed issue was closed this session
38
- let claimClosed = false;
42
+ // Check if an issue was closed this session (set by beads-claim-sync on bd close)
43
+ let closedIssueId = null;
39
44
  try {
40
- const out = execSync('bd list --status=closed', {
45
+ closedIssueId = execSync(`bd kv get "closed-this-session:${sessionId}"`, {
41
46
  encoding: 'utf8',
42
47
  cwd,
43
48
  stdio: ['pipe', 'pipe', 'pipe'],
44
- timeout: 8000,
45
- });
46
- claimClosed = out.includes(claimId);
47
- } catch {
49
+ timeout: 5000,
50
+ }).trim();
51
+ } catch (err) {
52
+ if (err.status === 1) {
53
+ // No closed-this-session marker → nothing to prompt about
54
+ process.exit(0);
55
+ }
48
56
  process.exit(0); // fail open
49
57
  }
50
58
 
51
- if (!claimClosed) process.exit(0);
59
+ if (!closedIssueId) process.exit(0);
52
60
 
53
61
  process.stderr.write(memoryPromptMessage());
54
62
  process.exit(2);
@@ -46,6 +46,7 @@ export const DANGEROUS_BASH_PATTERNS = [
46
46
  ];
47
47
 
48
48
  export const SAFE_BASH_PREFIXES = [
49
+ // Git read-only
49
50
  'git status',
50
51
  'git log',
51
52
  'git diff',
@@ -60,10 +61,40 @@ export const SAFE_BASH_PREFIXES = [
60
61
  'git worktree',
61
62
  'git checkout -b',
62
63
  'git switch -c',
64
+ // Tools
63
65
  'gh',
64
66
  'bd',
65
- 'touch .beads/',
66
67
  'npx gitnexus',
68
+ // Read-only filesystem
69
+ 'cat',
70
+ 'ls',
71
+ 'head',
72
+ 'tail',
73
+ 'pwd',
74
+ 'which',
75
+ 'type',
76
+ 'env',
77
+ 'printenv',
78
+ 'find',
79
+ 'grep',
80
+ 'rg',
81
+ 'fd',
82
+ 'wc',
83
+ 'sort',
84
+ 'uniq',
85
+ 'cut',
86
+ 'awk',
87
+ 'jq',
88
+ 'yq',
89
+ 'bat',
90
+ 'less',
91
+ 'more',
92
+ 'file',
93
+ 'stat',
94
+ 'du',
95
+ 'tree',
96
+ // Allowed writes (specific paths)
97
+ 'touch .beads/',
67
98
  ];
68
99
 
69
100
  export const NATIVE_TEAM_TOOLS = [
@@ -5,7 +5,8 @@
5
5
  // Installed by: xtrm install
6
6
 
7
7
  import { execSync } from 'node:child_process';
8
- import { readFileSync } from 'node:fs';
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
9
10
  import { WRITE_TOOLS, SAFE_BASH_PREFIXES } from './guard-rules.mjs';
10
11
 
11
12
  let branch = '';
@@ -35,6 +36,7 @@ try {
35
36
 
36
37
  const tool = input.tool_name ?? '';
37
38
  const hookEventName = input.hook_event_name ?? 'PreToolUse';
39
+ const cwd = input.cwd || process.cwd();
38
40
 
39
41
  function deny(reason) {
40
42
  process.stdout.write(JSON.stringify({
@@ -45,7 +47,23 @@ function deny(reason) {
45
47
  process.exit(0);
46
48
  }
47
49
 
50
+ // Check for existing worktree/session state
51
+ function getSessionState(cwd) {
52
+ const statePath = join(cwd, '.xtrm-session-state.json');
53
+ if (!existsSync(statePath)) return null;
54
+ try {
55
+ return JSON.parse(readFileSync(statePath, 'utf8'));
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
48
61
  if (WRITE_TOOLS.includes(tool)) {
62
+ const state = getSessionState(cwd);
63
+ if (state?.worktreePath) {
64
+ deny(`ā›” On '${branch}' — worktree already created.\n`
65
+ + ` cd ${state.worktreePath}\n`);
66
+ }
49
67
  deny(`ā›” On '${branch}' — start on a feature branch and claim an issue.\n`
50
68
  + ' git checkout -b feature/<name>\n'
51
69
  + ' bd update <id> --claim\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtrm-tools",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "license": "MIT",
6
6
  "type": "module",