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 +1 -1
- package/hooks/beads-claim-sync.mjs +17 -12
- package/hooks/beads-memory-gate.mjs +24 -16
- package/hooks/guard-rules.mjs +32 -1
- package/hooks/main-guard.mjs +19 -1
- package/package.json +1 -1
package/cli/package.json
CHANGED
|
@@ -197,20 +197,25 @@ function main() {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
//
|
|
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
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
//
|
|
31
|
-
|
|
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
|
-
|
|
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:
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
|
|
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 (!
|
|
59
|
+
if (!closedIssueId) process.exit(0);
|
|
52
60
|
|
|
53
61
|
process.stderr.write(memoryPromptMessage());
|
|
54
62
|
process.exit(2);
|
package/hooks/guard-rules.mjs
CHANGED
|
@@ -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 = [
|
package/hooks/main-guard.mjs
CHANGED
|
@@ -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');
|