xtrm-tools 0.5.45 → 0.5.47
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +24 -5
- package/cli/dist/index.cjs +9812 -9983
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/config/instructions/agents-top.md +2 -4
- package/config/instructions/claude-top.md +2 -4
- package/config/pi/extensions/beads/index.ts +18 -78
- package/config/pi/extensions/custom-footer/index.ts +2 -3
- package/config/pi/extensions/xtrm-ui/format.ts +93 -0
- package/config/pi/extensions/xtrm-ui/index.ts +1044 -0
- package/config/pi/extensions/xtrm-ui/package.json +10 -0
- package/config/pi/extensions/xtrm-ui/themes/pidex-dark.json +85 -0
- package/config/pi/extensions/xtrm-ui/themes/pidex-light.json +85 -0
- package/config/pi/install-schema.json +0 -1
- package/hooks/beads-claim-sync.mjs +15 -96
- package/hooks/beads-gate-messages.mjs +2 -4
- package/hooks/beads-gate-utils.mjs +0 -18
- package/hooks/statusline.mjs +5 -3
- package/package.json +1 -1
- package/plugins/xtrm-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/xtrm-tools/hooks/beads-claim-sync.mjs +15 -96
- package/plugins/xtrm-tools/hooks/beads-gate-messages.mjs +2 -4
- package/plugins/xtrm-tools/hooks/beads-gate-utils.mjs +0 -18
- package/plugins/xtrm-tools/hooks/statusline.mjs +5 -3
- package/plugins/xtrm-tools/skills/planning/SKILL.md +75 -20
- package/plugins/xtrm-tools/skills/using-xtrm/SKILL.md +1 -1
- package/plugins/xtrm-tools/skills/xt-debugging/SKILL.md +149 -0
- package/plugins/xtrm-tools/skills/xt-end/SKILL.md +28 -0
- package/skills/planning/SKILL.md +75 -20
- package/skills/using-xtrm/SKILL.md +1 -1
- package/skills/xt-debugging/SKILL.md +149 -0
- package/skills/xt-end/SKILL.md +28 -0
- package/plugins/xtrm-tools/skills/gitnexus-debugging/SKILL.md +0 -85
- package/skills/gitnexus-debugging/SKILL.md +0 -85
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "pidex-dark",
|
|
4
|
+
"vars": {
|
|
5
|
+
"accentBlue": "#b8d3ff",
|
|
6
|
+
"accentCyan": "#b8d3ff",
|
|
7
|
+
"accentTeal": "#c7d2e0",
|
|
8
|
+
"successGreen": "#9fd59f",
|
|
9
|
+
"errorRed": "#ff9a9a",
|
|
10
|
+
"warningAmber": "#d2b48c",
|
|
11
|
+
"surface": "#000000",
|
|
12
|
+
"surfaceAlt": "#111111",
|
|
13
|
+
"surfaceMuted": "#1a1a1a",
|
|
14
|
+
"surfaceUser": "#1f1f1f",
|
|
15
|
+
"surfaceCustom": "#161616",
|
|
16
|
+
"gray": "#a7a7a7",
|
|
17
|
+
"dimGray": "#8a8a8a",
|
|
18
|
+
"borderGray": "#666666",
|
|
19
|
+
"borderBright": "#9a9a9a"
|
|
20
|
+
},
|
|
21
|
+
"colors": {
|
|
22
|
+
"accent": "accentBlue",
|
|
23
|
+
"border": "borderGray",
|
|
24
|
+
"borderAccent": "borderBright",
|
|
25
|
+
"borderMuted": "borderGray",
|
|
26
|
+
"success": "successGreen",
|
|
27
|
+
"error": "errorRed",
|
|
28
|
+
"warning": "warningAmber",
|
|
29
|
+
"muted": "gray",
|
|
30
|
+
"dim": "dimGray",
|
|
31
|
+
"text": "",
|
|
32
|
+
"thinkingText": "gray",
|
|
33
|
+
|
|
34
|
+
"selectedBg": "surfaceMuted",
|
|
35
|
+
"userMessageBg": "surfaceUser",
|
|
36
|
+
"userMessageText": "",
|
|
37
|
+
"customMessageBg": "surfaceCustom",
|
|
38
|
+
"customMessageText": "",
|
|
39
|
+
"customMessageLabel": "accentBlue",
|
|
40
|
+
"toolPendingBg": "surfaceAlt",
|
|
41
|
+
"toolSuccessBg": "surfaceAlt",
|
|
42
|
+
"toolErrorBg": "surfaceAlt",
|
|
43
|
+
"toolTitle": "",
|
|
44
|
+
"toolOutput": "gray",
|
|
45
|
+
|
|
46
|
+
"mdHeading": "warningAmber",
|
|
47
|
+
"mdLink": "accentBlue",
|
|
48
|
+
"mdLinkUrl": "dimGray",
|
|
49
|
+
"mdCode": "accentCyan",
|
|
50
|
+
"mdCodeBlock": "gray",
|
|
51
|
+
"mdCodeBlockBorder": "borderGray",
|
|
52
|
+
"mdQuote": "gray",
|
|
53
|
+
"mdQuoteBorder": "borderGray",
|
|
54
|
+
"mdHr": "borderGray",
|
|
55
|
+
"mdListBullet": "accentTeal",
|
|
56
|
+
|
|
57
|
+
"toolDiffAdded": "successGreen",
|
|
58
|
+
"toolDiffRemoved": "errorRed",
|
|
59
|
+
"toolDiffContext": "gray",
|
|
60
|
+
|
|
61
|
+
"syntaxComment": "#6b7280",
|
|
62
|
+
"syntaxKeyword": "#7aa2f7",
|
|
63
|
+
"syntaxFunction": "#c0caf5",
|
|
64
|
+
"syntaxVariable": "#a9b1d6",
|
|
65
|
+
"syntaxString": "#9ece6a",
|
|
66
|
+
"syntaxNumber": "#ff9e64",
|
|
67
|
+
"syntaxType": "#73daca",
|
|
68
|
+
"syntaxOperator": "#c0caf5",
|
|
69
|
+
"syntaxPunctuation": "#8f9bb3",
|
|
70
|
+
|
|
71
|
+
"thinkingOff": "borderGray",
|
|
72
|
+
"thinkingMinimal": "#707070",
|
|
73
|
+
"thinkingLow": "#7a7a7a",
|
|
74
|
+
"thinkingMedium": "#858585",
|
|
75
|
+
"thinkingHigh": "#8f8f8f",
|
|
76
|
+
"thinkingXhigh": "#999999",
|
|
77
|
+
|
|
78
|
+
"bashMode": "accentTeal"
|
|
79
|
+
},
|
|
80
|
+
"export": {
|
|
81
|
+
"pageBg": "#12161d",
|
|
82
|
+
"cardBg": "#171b22",
|
|
83
|
+
"infoBg": "#28230f"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "pidex-light",
|
|
4
|
+
"vars": {
|
|
5
|
+
"accentBlue": "#44546a",
|
|
6
|
+
"accentCyan": "#5f6b7a",
|
|
7
|
+
"accentTeal": "#5f6b7a",
|
|
8
|
+
"successGreen": "#587558",
|
|
9
|
+
"errorRed": "#a85f5f",
|
|
10
|
+
"warningAmber": "#8e7348",
|
|
11
|
+
"surface": "#ffffff",
|
|
12
|
+
"surfaceAlt": "#f1f1f1",
|
|
13
|
+
"surfaceMuted": "#e6e6e6",
|
|
14
|
+
"surfaceUser": "#f0f0f0",
|
|
15
|
+
"surfaceCustom": "#f5f5f5",
|
|
16
|
+
"gray": "#5f5f5f",
|
|
17
|
+
"dimGray": "#7a7a7a",
|
|
18
|
+
"borderGray": "#b9b9b9",
|
|
19
|
+
"borderBright": "#9f9f9f"
|
|
20
|
+
},
|
|
21
|
+
"colors": {
|
|
22
|
+
"accent": "accentBlue",
|
|
23
|
+
"border": "borderGray",
|
|
24
|
+
"borderAccent": "borderBright",
|
|
25
|
+
"borderMuted": "borderGray",
|
|
26
|
+
"success": "successGreen",
|
|
27
|
+
"error": "errorRed",
|
|
28
|
+
"warning": "warningAmber",
|
|
29
|
+
"muted": "gray",
|
|
30
|
+
"dim": "dimGray",
|
|
31
|
+
"text": "",
|
|
32
|
+
"thinkingText": "gray",
|
|
33
|
+
|
|
34
|
+
"selectedBg": "surfaceMuted",
|
|
35
|
+
"userMessageBg": "surfaceUser",
|
|
36
|
+
"userMessageText": "",
|
|
37
|
+
"customMessageBg": "surfaceCustom",
|
|
38
|
+
"customMessageText": "",
|
|
39
|
+
"customMessageLabel": "accentBlue",
|
|
40
|
+
"toolPendingBg": "surfaceAlt",
|
|
41
|
+
"toolSuccessBg": "surfaceAlt",
|
|
42
|
+
"toolErrorBg": "surfaceAlt",
|
|
43
|
+
"toolTitle": "",
|
|
44
|
+
"toolOutput": "gray",
|
|
45
|
+
|
|
46
|
+
"mdHeading": "warningAmber",
|
|
47
|
+
"mdLink": "accentBlue",
|
|
48
|
+
"mdLinkUrl": "dimGray",
|
|
49
|
+
"mdCode": "accentCyan",
|
|
50
|
+
"mdCodeBlock": "gray",
|
|
51
|
+
"mdCodeBlockBorder": "borderGray",
|
|
52
|
+
"mdQuote": "gray",
|
|
53
|
+
"mdQuoteBorder": "borderGray",
|
|
54
|
+
"mdHr": "borderGray",
|
|
55
|
+
"mdListBullet": "accentTeal",
|
|
56
|
+
|
|
57
|
+
"toolDiffAdded": "successGreen",
|
|
58
|
+
"toolDiffRemoved": "errorRed",
|
|
59
|
+
"toolDiffContext": "gray",
|
|
60
|
+
|
|
61
|
+
"syntaxComment": "#6b7280",
|
|
62
|
+
"syntaxKeyword": "#3569c8",
|
|
63
|
+
"syntaxFunction": "#3b4351",
|
|
64
|
+
"syntaxVariable": "#364152",
|
|
65
|
+
"syntaxString": "#3f7d3d",
|
|
66
|
+
"syntaxNumber": "#b06500",
|
|
67
|
+
"syntaxType": "#2c7a7b",
|
|
68
|
+
"syntaxOperator": "#3b4351",
|
|
69
|
+
"syntaxPunctuation": "#55606f",
|
|
70
|
+
|
|
71
|
+
"thinkingOff": "borderGray",
|
|
72
|
+
"thinkingMinimal": "#9d9d9d",
|
|
73
|
+
"thinkingLow": "#979797",
|
|
74
|
+
"thinkingMedium": "#919191",
|
|
75
|
+
"thinkingHigh": "#8b8b8b",
|
|
76
|
+
"thinkingXhigh": "#858585",
|
|
77
|
+
|
|
78
|
+
"bashMode": "accentTeal"
|
|
79
|
+
},
|
|
80
|
+
"export": {
|
|
81
|
+
"pageBg": "#f5f7fa",
|
|
82
|
+
"cardBg": "#ffffff",
|
|
83
|
+
"infoBg": "#fff5e6"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// beads-claim-sync — PostToolUse hook
|
|
3
3
|
// bd update --claim → set kv claim
|
|
4
|
-
// bd close →
|
|
4
|
+
// bd close → set closed-this-session kv for memory gate
|
|
5
5
|
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { readFileSync, existsSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
@@ -33,6 +33,14 @@ function resolveMainRoot(cwd) {
|
|
|
33
33
|
return cwd;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Returns a per-session claim filename: 'statusline-claim' for the main session,
|
|
37
|
+
// 'statusline-claim-<worktreeName>' for worktree sessions.
|
|
38
|
+
// Prevents cross-session contamination when multiple worktrees run simultaneously.
|
|
39
|
+
function resolveClaimFileName(cwd) {
|
|
40
|
+
const m = cwd.match(/\/\.xtrm\/worktrees\/([^/]+)/);
|
|
41
|
+
return m ? `statusline-claim-${m[1]}` : 'statusline-claim';
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
function isShellTool(toolName) {
|
|
37
45
|
return toolName === 'Bash' || toolName === 'bash' || toolName === 'execute_shell_command';
|
|
38
46
|
}
|
|
@@ -50,73 +58,7 @@ function commandSucceeded(payload) {
|
|
|
50
58
|
return true;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
function runGit(args, cwd, timeout = 8000) {
|
|
54
|
-
return spawnSync('git', args, {
|
|
55
|
-
cwd,
|
|
56
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
57
|
-
encoding: 'utf8',
|
|
58
|
-
timeout,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function runBd(args, cwd, timeout = 5000) {
|
|
63
|
-
return spawnSync('bd', args, {
|
|
64
|
-
cwd,
|
|
65
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
-
encoding: 'utf8',
|
|
67
|
-
timeout,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function hasGitChanges(cwd) {
|
|
72
|
-
const result = runGit(['status', '--porcelain'], cwd);
|
|
73
|
-
if (result.status !== 0) return false;
|
|
74
|
-
return result.stdout.trim().length > 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function getCloseReason(cwd, issueId, command) {
|
|
78
|
-
// 1. Parse --reason "..." from the command itself (fastest, no extra call)
|
|
79
|
-
const reasonMatch = command.match(/--reason[=\s]+["']([^"']+)["']/);
|
|
80
|
-
if (reasonMatch) return reasonMatch[1].trim();
|
|
81
|
-
|
|
82
|
-
// 2. Fall back to bd show <id> --json
|
|
83
|
-
const show = runBd(['show', issueId, '--json'], cwd);
|
|
84
|
-
if (show.status === 0 && show.stdout) {
|
|
85
|
-
try {
|
|
86
|
-
const parsed = JSON.parse(show.stdout);
|
|
87
|
-
const reason = parsed?.[0]?.close_reason;
|
|
88
|
-
if (typeof reason === 'string' && reason.trim().length > 0) return reason.trim();
|
|
89
|
-
} catch { /* fall through */ }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return `Close ${issueId}`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function stageUntracked(cwd) {
|
|
96
|
-
const result = runGit(['ls-files', '--others', '--exclude-standard'], cwd);
|
|
97
|
-
if (result.status !== 0) return;
|
|
98
|
-
const untracked = result.stdout.trim().split('\n').filter(Boolean);
|
|
99
|
-
if (untracked.length === 0) return;
|
|
100
|
-
runGit(['add', '--', ...untracked], cwd);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function autoCommit(cwd, issueId, command) {
|
|
104
|
-
if (!hasGitChanges(cwd)) {
|
|
105
|
-
return { ok: true, message: 'No changes detected — auto-commit skipped.' };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
stageUntracked(cwd);
|
|
109
|
-
|
|
110
|
-
const reason = getCloseReason(cwd, issueId, command);
|
|
111
|
-
const commitMessage = `${reason} (${issueId})`;
|
|
112
|
-
const result = runGit(['commit', '--no-verify', '-am', commitMessage], cwd, 15000);
|
|
113
|
-
if (result.status !== 0) {
|
|
114
|
-
const err = (result.stderr || result.stdout || '').trim();
|
|
115
|
-
return { ok: false, message: `Auto-commit failed: ${err || 'unknown error'}` };
|
|
116
|
-
}
|
|
117
61
|
|
|
118
|
-
return { ok: true, message: `Auto-committed: \`${commitMessage}\`` };
|
|
119
|
-
}
|
|
120
62
|
|
|
121
63
|
|
|
122
64
|
function main() {
|
|
@@ -147,11 +89,11 @@ function main() {
|
|
|
147
89
|
process.exit(0);
|
|
148
90
|
}
|
|
149
91
|
|
|
150
|
-
// Write claim state for statusline —
|
|
92
|
+
// Write claim state for statusline — per-worktree file under main root.
|
|
151
93
|
try {
|
|
152
94
|
const xtrmDir = join(resolveMainRoot(cwd), '.xtrm');
|
|
153
95
|
mkdirSync(xtrmDir, { recursive: true });
|
|
154
|
-
writeFileSync(join(xtrmDir,
|
|
96
|
+
writeFileSync(join(xtrmDir, resolveClaimFileName(cwd)), issueId);
|
|
155
97
|
} catch { /* non-fatal */ }
|
|
156
98
|
|
|
157
99
|
logEvent({
|
|
@@ -172,16 +114,13 @@ function main() {
|
|
|
172
114
|
}
|
|
173
115
|
}
|
|
174
116
|
|
|
175
|
-
// On bd close:
|
|
117
|
+
// On bd close: mark closed-this-session for memory gate
|
|
176
118
|
if (/\bbd\s+close\b/.test(command) && commandSucceeded(input)) {
|
|
177
119
|
const match = command.match(/\bbd\s+close\s+(\S+)/);
|
|
178
120
|
const closedIssueId = match?.[1];
|
|
179
121
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Clear claim state for statusline — use main root so worktree and main sessions agree.
|
|
184
|
-
try { unlinkSync(join(resolveMainRoot(cwd), '.xtrm', 'statusline-claim')); } catch { /* ok if missing */ }
|
|
122
|
+
// Clear claim state for statusline — per-worktree file under main root.
|
|
123
|
+
try { unlinkSync(join(resolveMainRoot(cwd), '.xtrm', resolveClaimFileName(cwd))); } catch { /* ok if missing */ }
|
|
185
124
|
|
|
186
125
|
// Mark this issue as closed this session (memory gate reads this)
|
|
187
126
|
if (closedIssueId) {
|
|
@@ -190,10 +129,7 @@ function main() {
|
|
|
190
129
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
191
130
|
timeout: 5000,
|
|
192
131
|
});
|
|
193
|
-
}
|
|
194
132
|
|
|
195
|
-
// Log bd lifecycle events
|
|
196
|
-
if (closedIssueId) {
|
|
197
133
|
logEvent({
|
|
198
134
|
cwd,
|
|
199
135
|
runtime: 'claude',
|
|
@@ -204,26 +140,9 @@ function main() {
|
|
|
204
140
|
issueId: closedIssueId,
|
|
205
141
|
});
|
|
206
142
|
}
|
|
207
|
-
if (commit) {
|
|
208
|
-
logEvent({
|
|
209
|
-
cwd,
|
|
210
|
-
runtime: 'claude',
|
|
211
|
-
sessionId,
|
|
212
|
-
layer: 'bd',
|
|
213
|
-
kind: 'bd.committed',
|
|
214
|
-
outcome: commit.ok ? 'allow' : 'block',
|
|
215
|
-
issueId: closedIssueId ?? null,
|
|
216
|
-
data: { msg: commit.message },
|
|
217
|
-
extra: { ok: commit.ok },
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const commitLine = commit
|
|
222
|
-
? `\n${commit.ok ? '✅' : '⚠️'} **Session Flow**: ${commit.message}`
|
|
223
|
-
: '';
|
|
224
143
|
|
|
225
144
|
process.stdout.write(JSON.stringify({
|
|
226
|
-
additionalContext: `\n🔓 **Beads**: Issue closed
|
|
145
|
+
additionalContext: `\n🔓 **Beads**: Issue closed. Evaluate insights, then acknowledge:\n \`bd remember "<insight>"\` (or note "nothing to persist")`,
|
|
227
146
|
}));
|
|
228
147
|
process.stdout.write('\n');
|
|
229
148
|
process.exit(0);
|
|
@@ -12,7 +12,7 @@ export const SESSION_CLOSE_PROTOCOL =
|
|
|
12
12
|
' xt end\n';
|
|
13
13
|
|
|
14
14
|
export const COMMIT_NEXT_STEPS =
|
|
15
|
-
' bd close <id> --reason="..." ← closes issue
|
|
15
|
+
' bd close <id> --reason="..." ← closes issue\n' +
|
|
16
16
|
' xt end ← push, PR, merge, worktree cleanup\n';
|
|
17
17
|
|
|
18
18
|
// ── Edit gate messages ───────────────────────────────────────────
|
|
@@ -58,9 +58,7 @@ export function stopBlockMessage(summary, claimed) {
|
|
|
58
58
|
|
|
59
59
|
export function memoryPromptMessage(claimId, sessionId) {
|
|
60
60
|
const claimLine = claimId ? `claim \`${claimId}\` was closed.\n` : '';
|
|
61
|
-
const ackCmd = sessionId
|
|
62
|
-
? `bd kv set "memory-gate-done:${sessionId}"`
|
|
63
|
-
: 'touch .beads/.memory-gate-done';
|
|
61
|
+
const ackCmd = `bd kv set "memory-gate-done:${sessionId}"`;
|
|
64
62
|
return (
|
|
65
63
|
`\u25cf Memory gate: ${claimLine}` +
|
|
66
64
|
'For each candidate insight, check ALL 4:\n' +
|
|
@@ -133,24 +133,6 @@ export function getClosedThisSession(sessionId, cwd) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
/**
|
|
137
|
-
* Return true if the memory gate is pending acknowledgment for this session.
|
|
138
|
-
* Pending = closed-this-session kv is set AND .beads/.memory-gate-done marker is absent.
|
|
139
|
-
*/
|
|
140
|
-
export function isMemoryGatePending(sessionId, cwd) {
|
|
141
|
-
if (existsSync(join(cwd, '.beads', '.memory-gate-done'))) return false;
|
|
142
|
-
const closed = getClosedThisSession(sessionId, cwd);
|
|
143
|
-
return !!closed; // null (unavailable) is falsy → fail open
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Return true if a Bash command is a memory-gate acknowledgment command.
|
|
148
|
-
* These commands are allowed even while the memory gate is pending.
|
|
149
|
-
*/
|
|
150
|
-
export function isMemoryAckCommand(command) {
|
|
151
|
-
return /\bbd\s+remember\b/.test(command) || /\btouch\s+\.beads\/\.memory-gate-done\b/.test(command);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
136
|
/**
|
|
155
137
|
* If cwd is inside a .xtrm/worktrees/<name> directory, return the worktree root path.
|
|
156
138
|
* Returns null if not in a worktree.
|
package/hooks/statusline.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Line 2: ◐ italic(claim title) OR ○ bold(N) open — no background
|
|
5
5
|
//
|
|
6
6
|
// Colors: bold/dim/italic only — no explicit fg/bg, adapts to dark & light themes.
|
|
7
|
-
// State: .xtrm/statusline-claim (written by beads-claim-sync.mjs)
|
|
7
|
+
// State: .xtrm/statusline-claim[-<worktreeName>] (written by beads-claim-sync.mjs)
|
|
8
8
|
// Cache: /tmp per cwd, 5s TTL
|
|
9
9
|
|
|
10
10
|
import { execSync } from 'node:child_process';
|
|
@@ -125,8 +125,10 @@ if (!data) {
|
|
|
125
125
|
let claimTitle = null;
|
|
126
126
|
let openCount = 0;
|
|
127
127
|
if (existsSync(join(cwd, '.beads')) || existsSync(join(mainRoot, '.beads'))) {
|
|
128
|
-
//
|
|
129
|
-
const
|
|
128
|
+
// Per-worktree claim file — prevents cross-session contamination.
|
|
129
|
+
const worktreeMatch = cwd.match(/\/\.xtrm\/worktrees\/([^/]+)/);
|
|
130
|
+
const claimFileName = worktreeMatch ? `statusline-claim-${worktreeMatch[1]}` : 'statusline-claim';
|
|
131
|
+
const claimFile = join(mainRoot, '.xtrm', claimFileName);
|
|
130
132
|
claimId = existsSync(claimFile) ? (readFileSync(claimFile, 'utf8').trim() || null) : null;
|
|
131
133
|
if (claimId) {
|
|
132
134
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// beads-claim-sync — PostToolUse hook
|
|
3
3
|
// bd update --claim → set kv claim
|
|
4
|
-
// bd close →
|
|
4
|
+
// bd close → set closed-this-session kv for memory gate
|
|
5
5
|
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { readFileSync, existsSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
@@ -33,6 +33,14 @@ function resolveMainRoot(cwd) {
|
|
|
33
33
|
return cwd;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Returns a per-session claim filename: 'statusline-claim' for the main session,
|
|
37
|
+
// 'statusline-claim-<worktreeName>' for worktree sessions.
|
|
38
|
+
// Prevents cross-session contamination when multiple worktrees run simultaneously.
|
|
39
|
+
function resolveClaimFileName(cwd) {
|
|
40
|
+
const m = cwd.match(/\/\.xtrm\/worktrees\/([^/]+)/);
|
|
41
|
+
return m ? `statusline-claim-${m[1]}` : 'statusline-claim';
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
function isShellTool(toolName) {
|
|
37
45
|
return toolName === 'Bash' || toolName === 'bash' || toolName === 'execute_shell_command';
|
|
38
46
|
}
|
|
@@ -50,73 +58,7 @@ function commandSucceeded(payload) {
|
|
|
50
58
|
return true;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
function runGit(args, cwd, timeout = 8000) {
|
|
54
|
-
return spawnSync('git', args, {
|
|
55
|
-
cwd,
|
|
56
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
57
|
-
encoding: 'utf8',
|
|
58
|
-
timeout,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function runBd(args, cwd, timeout = 5000) {
|
|
63
|
-
return spawnSync('bd', args, {
|
|
64
|
-
cwd,
|
|
65
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
-
encoding: 'utf8',
|
|
67
|
-
timeout,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function hasGitChanges(cwd) {
|
|
72
|
-
const result = runGit(['status', '--porcelain'], cwd);
|
|
73
|
-
if (result.status !== 0) return false;
|
|
74
|
-
return result.stdout.trim().length > 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function getCloseReason(cwd, issueId, command) {
|
|
78
|
-
// 1. Parse --reason "..." from the command itself (fastest, no extra call)
|
|
79
|
-
const reasonMatch = command.match(/--reason[=\s]+["']([^"']+)["']/);
|
|
80
|
-
if (reasonMatch) return reasonMatch[1].trim();
|
|
81
|
-
|
|
82
|
-
// 2. Fall back to bd show <id> --json
|
|
83
|
-
const show = runBd(['show', issueId, '--json'], cwd);
|
|
84
|
-
if (show.status === 0 && show.stdout) {
|
|
85
|
-
try {
|
|
86
|
-
const parsed = JSON.parse(show.stdout);
|
|
87
|
-
const reason = parsed?.[0]?.close_reason;
|
|
88
|
-
if (typeof reason === 'string' && reason.trim().length > 0) return reason.trim();
|
|
89
|
-
} catch { /* fall through */ }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return `Close ${issueId}`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function stageUntracked(cwd) {
|
|
96
|
-
const result = runGit(['ls-files', '--others', '--exclude-standard'], cwd);
|
|
97
|
-
if (result.status !== 0) return;
|
|
98
|
-
const untracked = result.stdout.trim().split('\n').filter(Boolean);
|
|
99
|
-
if (untracked.length === 0) return;
|
|
100
|
-
runGit(['add', '--', ...untracked], cwd);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function autoCommit(cwd, issueId, command) {
|
|
104
|
-
if (!hasGitChanges(cwd)) {
|
|
105
|
-
return { ok: true, message: 'No changes detected — auto-commit skipped.' };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
stageUntracked(cwd);
|
|
109
|
-
|
|
110
|
-
const reason = getCloseReason(cwd, issueId, command);
|
|
111
|
-
const commitMessage = `${reason} (${issueId})`;
|
|
112
|
-
const result = runGit(['commit', '--no-verify', '-am', commitMessage], cwd, 15000);
|
|
113
|
-
if (result.status !== 0) {
|
|
114
|
-
const err = (result.stderr || result.stdout || '').trim();
|
|
115
|
-
return { ok: false, message: `Auto-commit failed: ${err || 'unknown error'}` };
|
|
116
|
-
}
|
|
117
61
|
|
|
118
|
-
return { ok: true, message: `Auto-committed: \`${commitMessage}\`` };
|
|
119
|
-
}
|
|
120
62
|
|
|
121
63
|
|
|
122
64
|
function main() {
|
|
@@ -147,11 +89,11 @@ function main() {
|
|
|
147
89
|
process.exit(0);
|
|
148
90
|
}
|
|
149
91
|
|
|
150
|
-
// Write claim state for statusline —
|
|
92
|
+
// Write claim state for statusline — per-worktree file under main root.
|
|
151
93
|
try {
|
|
152
94
|
const xtrmDir = join(resolveMainRoot(cwd), '.xtrm');
|
|
153
95
|
mkdirSync(xtrmDir, { recursive: true });
|
|
154
|
-
writeFileSync(join(xtrmDir,
|
|
96
|
+
writeFileSync(join(xtrmDir, resolveClaimFileName(cwd)), issueId);
|
|
155
97
|
} catch { /* non-fatal */ }
|
|
156
98
|
|
|
157
99
|
logEvent({
|
|
@@ -172,16 +114,13 @@ function main() {
|
|
|
172
114
|
}
|
|
173
115
|
}
|
|
174
116
|
|
|
175
|
-
// On bd close:
|
|
117
|
+
// On bd close: mark closed-this-session for memory gate
|
|
176
118
|
if (/\bbd\s+close\b/.test(command) && commandSucceeded(input)) {
|
|
177
119
|
const match = command.match(/\bbd\s+close\s+(\S+)/);
|
|
178
120
|
const closedIssueId = match?.[1];
|
|
179
121
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Clear claim state for statusline — use main root so worktree and main sessions agree.
|
|
184
|
-
try { unlinkSync(join(resolveMainRoot(cwd), '.xtrm', 'statusline-claim')); } catch { /* ok if missing */ }
|
|
122
|
+
// Clear claim state for statusline — per-worktree file under main root.
|
|
123
|
+
try { unlinkSync(join(resolveMainRoot(cwd), '.xtrm', resolveClaimFileName(cwd))); } catch { /* ok if missing */ }
|
|
185
124
|
|
|
186
125
|
// Mark this issue as closed this session (memory gate reads this)
|
|
187
126
|
if (closedIssueId) {
|
|
@@ -190,10 +129,7 @@ function main() {
|
|
|
190
129
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
191
130
|
timeout: 5000,
|
|
192
131
|
});
|
|
193
|
-
}
|
|
194
132
|
|
|
195
|
-
// Log bd lifecycle events
|
|
196
|
-
if (closedIssueId) {
|
|
197
133
|
logEvent({
|
|
198
134
|
cwd,
|
|
199
135
|
runtime: 'claude',
|
|
@@ -204,26 +140,9 @@ function main() {
|
|
|
204
140
|
issueId: closedIssueId,
|
|
205
141
|
});
|
|
206
142
|
}
|
|
207
|
-
if (commit) {
|
|
208
|
-
logEvent({
|
|
209
|
-
cwd,
|
|
210
|
-
runtime: 'claude',
|
|
211
|
-
sessionId,
|
|
212
|
-
layer: 'bd',
|
|
213
|
-
kind: 'bd.committed',
|
|
214
|
-
outcome: commit.ok ? 'allow' : 'block',
|
|
215
|
-
issueId: closedIssueId ?? null,
|
|
216
|
-
data: { msg: commit.message },
|
|
217
|
-
extra: { ok: commit.ok },
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const commitLine = commit
|
|
222
|
-
? `\n${commit.ok ? '✅' : '⚠️'} **Session Flow**: ${commit.message}`
|
|
223
|
-
: '';
|
|
224
143
|
|
|
225
144
|
process.stdout.write(JSON.stringify({
|
|
226
|
-
additionalContext: `\n🔓 **Beads**: Issue closed
|
|
145
|
+
additionalContext: `\n🔓 **Beads**: Issue closed. Evaluate insights, then acknowledge:\n \`bd remember "<insight>"\` (or note "nothing to persist")`,
|
|
227
146
|
}));
|
|
228
147
|
process.stdout.write('\n');
|
|
229
148
|
process.exit(0);
|
|
@@ -12,7 +12,7 @@ export const SESSION_CLOSE_PROTOCOL =
|
|
|
12
12
|
' xt end\n';
|
|
13
13
|
|
|
14
14
|
export const COMMIT_NEXT_STEPS =
|
|
15
|
-
' bd close <id> --reason="..." ← closes issue
|
|
15
|
+
' bd close <id> --reason="..." ← closes issue\n' +
|
|
16
16
|
' xt end ← push, PR, merge, worktree cleanup\n';
|
|
17
17
|
|
|
18
18
|
// ── Edit gate messages ───────────────────────────────────────────
|
|
@@ -58,9 +58,7 @@ export function stopBlockMessage(summary, claimed) {
|
|
|
58
58
|
|
|
59
59
|
export function memoryPromptMessage(claimId, sessionId) {
|
|
60
60
|
const claimLine = claimId ? `claim \`${claimId}\` was closed.\n` : '';
|
|
61
|
-
const ackCmd = sessionId
|
|
62
|
-
? `bd kv set "memory-gate-done:${sessionId}"`
|
|
63
|
-
: 'touch .beads/.memory-gate-done';
|
|
61
|
+
const ackCmd = `bd kv set "memory-gate-done:${sessionId}"`;
|
|
64
62
|
return (
|
|
65
63
|
`\u25cf Memory gate: ${claimLine}` +
|
|
66
64
|
'For each candidate insight, check ALL 4:\n' +
|
|
@@ -133,24 +133,6 @@ export function getClosedThisSession(sessionId, cwd) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
/**
|
|
137
|
-
* Return true if the memory gate is pending acknowledgment for this session.
|
|
138
|
-
* Pending = closed-this-session kv is set AND .beads/.memory-gate-done marker is absent.
|
|
139
|
-
*/
|
|
140
|
-
export function isMemoryGatePending(sessionId, cwd) {
|
|
141
|
-
if (existsSync(join(cwd, '.beads', '.memory-gate-done'))) return false;
|
|
142
|
-
const closed = getClosedThisSession(sessionId, cwd);
|
|
143
|
-
return !!closed; // null (unavailable) is falsy → fail open
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Return true if a Bash command is a memory-gate acknowledgment command.
|
|
148
|
-
* These commands are allowed even while the memory gate is pending.
|
|
149
|
-
*/
|
|
150
|
-
export function isMemoryAckCommand(command) {
|
|
151
|
-
return /\bbd\s+remember\b/.test(command) || /\btouch\s+\.beads\/\.memory-gate-done\b/.test(command);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
136
|
/**
|
|
155
137
|
* If cwd is inside a .xtrm/worktrees/<name> directory, return the worktree root path.
|
|
156
138
|
* Returns null if not in a worktree.
|