xtrm-tools 0.5.24 → 0.5.25
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/README.md +9 -3
- package/cli/dist/index.cjs +146 -190
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/config/hooks.json +8 -0
- package/hooks/beads-claim-sync.mjs +1 -1
- package/hooks/beads-commit-gate.mjs +2 -2
- package/hooks/beads-edit-gate.mjs +3 -3
- package/hooks/beads-memory-gate.mjs +2 -2
- package/hooks/beads-stop-gate.mjs +2 -2
- package/hooks/xtrm-logger.mjs +84 -91
- package/hooks/xtrm-session-logger.mjs +27 -0
- package/hooks/xtrm-tool-logger.mjs +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Dual-runtime workflow system** — Claude Code plugin + Pi extension suite for workflow enforcement, code quality gates, issue tracking, and development automation.
|
|
4
4
|
|
|
5
|
-
**Version 0.5.
|
|
5
|
+
**Version 0.5.24** | [Complete Guide](XTRM-GUIDE.md) | [Changelog](CHANGELOG.md)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -80,9 +80,10 @@ Policies are the **single source of truth** for all enforcement rules. Located i
|
|
|
80
80
|
| `session-flow.json` | both | Claim sync, stop gate, `xt end` reminder |
|
|
81
81
|
| `beads.json` | both | Issue tracking gates |
|
|
82
82
|
| `quality-gates.json` | both | Linting/typechecking |
|
|
83
|
-
| `
|
|
83
|
+
| `quality-gates-env.json` | both | Warns if tsc/ruff/eslint missing at session start |
|
|
84
|
+
| `using-xtrm.json` | claude | Injects using-xtrm session manual at SessionStart |
|
|
84
85
|
| `gitnexus.json` | claude | Knowledge graph enrichment |
|
|
85
|
-
| `
|
|
86
|
+
| `worktree-boundary.json` | claude | Blocks edits outside worktree when in `.xtrm/worktrees` |
|
|
86
87
|
| `service-skills.json` | pi | Territory-based skill activation |
|
|
87
88
|
|
|
88
89
|
### Compiler
|
|
@@ -111,6 +112,8 @@ xtrm <command> [options]
|
|
|
111
112
|
| `worktree clean` | Remove worktrees whose branch has been merged |
|
|
112
113
|
| `claude` | Launch Claude Code in a sandboxed worktree |
|
|
113
114
|
| `pi` | Launch Pi in a sandboxed worktree |
|
|
115
|
+
| `docs show` | Display frontmatter for README, CHANGELOG, docs/*.md |
|
|
116
|
+
| `debug` | Watch xtrm hook and bd lifecycle events in real time |
|
|
114
117
|
|
|
115
118
|
### Flags
|
|
116
119
|
|
|
@@ -186,6 +189,9 @@ bd close <id> --reason "Done" # Close when done
|
|
|
186
189
|
|
|
187
190
|
| Version | Date | Highlights |
|
|
188
191
|
|---------|------|------------|
|
|
192
|
+
| 0.5.24 | 2026-03-21 | Hash-based docs drift detection; CLI docs cleanup; `docs` and `debug` commands documented |
|
|
193
|
+
| 0.5.23 | 2026-03-21 | `xtrm debug` command for real-time event monitoring |
|
|
194
|
+
| 0.5.20 | 2026-03-21 | `xtrm docs show` command; worktree-boundary hook; statusline injection |
|
|
189
195
|
| 0.5.10 | 2026-03-21 | Install cleanup: removes stale `~/.claude/hooks/` + `~/.claude/skills/`; Qwen/Gemini dead code removed |
|
|
190
196
|
| 0.5.9 | 2026-03-20 | Worktrees moved inside repo under `.xtrm/worktrees/` |
|
|
191
197
|
| 0.5.8 | 2026-03-20 | session-flow rewrite: claim sync, stop gate, `xt end` reminder |
|
package/cli/dist/index.cjs
CHANGED
|
@@ -41303,14 +41303,15 @@ function resolveStatuslineScript() {
|
|
|
41303
41303
|
const pluginsFile = import_node_path5.default.join((0, import_node_os5.homedir)(), ".claude", "plugins", "installed_plugins.json");
|
|
41304
41304
|
try {
|
|
41305
41305
|
const plugins = JSON.parse((0, import_node_fs4.readFileSync)(pluginsFile, "utf8"));
|
|
41306
|
-
const entries
|
|
41307
|
-
|
|
41308
|
-
|
|
41306
|
+
for (const [key, entries] of Object.entries(plugins?.plugins ?? {})) {
|
|
41307
|
+
if (!key.startsWith("xtrm-tools@") || !entries?.length) continue;
|
|
41308
|
+
const p = import_node_path5.default.join(entries[0].installPath, "hooks", "statusline.mjs");
|
|
41309
|
+
if ((0, import_node_fs4.existsSync)(p)) return p;
|
|
41309
41310
|
}
|
|
41310
41311
|
} catch {
|
|
41311
41312
|
}
|
|
41312
41313
|
const fallback = import_node_path5.default.join((0, import_node_os5.homedir)(), ".claude", "hooks", "statusline.mjs");
|
|
41313
|
-
return fallback;
|
|
41314
|
+
return (0, import_node_fs4.existsSync)(fallback) ? fallback : null;
|
|
41314
41315
|
}
|
|
41315
41316
|
async function launchWorktreeSession(opts) {
|
|
41316
41317
|
const { runtime, name } = opts;
|
|
@@ -55983,11 +55984,12 @@ var import_path18 = __toESM(require("path"), 1);
|
|
|
55983
55984
|
var import_fs_extra17 = __toESM(require_lib2(), 1);
|
|
55984
55985
|
var HOOK_CATALOG = [
|
|
55985
55986
|
{ file: "using-xtrm-reminder.mjs", event: "SessionStart", desc: "Injects using-xtrm session operating manual into system prompt" },
|
|
55986
|
-
{ file: "serena-workflow-reminder.py", event: "SessionStart", desc: "Injects Serena semantic editing workflow reminder" },
|
|
55987
55987
|
{ file: "gitnexus/gitnexus-hook.cjs", event: "PostToolUse", desc: "Adds GitNexus context for search and Serena tooling" },
|
|
55988
|
-
{ file: "branch-state.mjs", event: "UserPromptSubmit", desc: "Injects current git branch into prompt context" },
|
|
55989
55988
|
{ file: "quality-check.cjs", event: "PostToolUse", desc: "Runs JS/TS quality checks on mutating edits" },
|
|
55990
55989
|
{ file: "quality-check.py", event: "PostToolUse", desc: "Runs Python quality checks on mutating edits" },
|
|
55990
|
+
{ file: "quality-check-env.mjs", event: "SessionStart", desc: "Warns if tsc/ruff/eslint are missing at session start" },
|
|
55991
|
+
{ file: "worktree-boundary.mjs", event: "PreToolUse", desc: "Blocks edits outside .xtrm/worktrees when in worktree session" },
|
|
55992
|
+
{ file: "statusline.mjs", event: "statusLine", desc: "Renders 2-line status: XTRM model branch + claim/open issues" },
|
|
55991
55993
|
{ file: "beads-edit-gate.mjs", event: "PreToolUse", desc: "Blocks file edits if no beads issue is claimed", beads: true },
|
|
55992
55994
|
{ file: "beads-commit-gate.mjs", event: "PreToolUse", desc: "Blocks commits when no beads issue is in progress", beads: true },
|
|
55993
55995
|
{ file: "beads-stop-gate.mjs", event: "Stop", desc: "Blocks stop when there is an unclosed in_progress claim", beads: true },
|
|
@@ -56009,22 +56011,6 @@ async function readSkillsFromDir(dir) {
|
|
|
56009
56011
|
}
|
|
56010
56012
|
return skills;
|
|
56011
56013
|
}
|
|
56012
|
-
async function readProjectSkillsFromDir(dir) {
|
|
56013
|
-
if (!await import_fs_extra17.default.pathExists(dir)) return [];
|
|
56014
|
-
const entries = await import_fs_extra17.default.readdir(dir);
|
|
56015
|
-
const skills = [];
|
|
56016
|
-
for (const name of entries.sort()) {
|
|
56017
|
-
const readme = import_path18.default.join(dir, name, "README.md");
|
|
56018
|
-
if (!await import_fs_extra17.default.pathExists(readme)) continue;
|
|
56019
|
-
const content = await import_fs_extra17.default.readFile(readme, "utf8");
|
|
56020
|
-
const descLine = content.split("\n").find((line) => {
|
|
56021
|
-
const trimmed = line.trim();
|
|
56022
|
-
return Boolean(trimmed) && !trimmed.startsWith("#") && !trimmed.startsWith("[") && !trimmed.startsWith("<");
|
|
56023
|
-
}) || "";
|
|
56024
|
-
skills.push({ name, desc: descLine.replace(/[*_`]/g, "").trim() });
|
|
56025
|
-
}
|
|
56026
|
-
return skills;
|
|
56027
|
-
}
|
|
56028
56014
|
function resolvePkgRootFallback() {
|
|
56029
56015
|
const candidates = [
|
|
56030
56016
|
import_path18.default.resolve(__dirname, "../.."),
|
|
@@ -56048,9 +56034,7 @@ function createHelpCommand() {
|
|
|
56048
56034
|
}
|
|
56049
56035
|
const pkgRoot = resolvePkgRootFallback();
|
|
56050
56036
|
const skillsRoot = repoRoot || pkgRoot || "";
|
|
56051
|
-
const projectSkillsRoot = repoRoot || pkgRoot || "";
|
|
56052
56037
|
const skills = skillsRoot ? await readSkillsFromDir(import_path18.default.join(skillsRoot, "skills")) : [];
|
|
56053
|
-
const projectSkills = projectSkillsRoot ? await readProjectSkillsFromDir(import_path18.default.join(projectSkillsRoot, "project-skills")) : [];
|
|
56054
56038
|
const W = 80;
|
|
56055
56039
|
const hr = kleur_default.dim("-".repeat(W));
|
|
56056
56040
|
const section = (title) => `
|
|
@@ -56059,24 +56043,11 @@ ${hr}`;
|
|
|
56059
56043
|
const installSection = [
|
|
56060
56044
|
section("INSTALL COMMANDS"),
|
|
56061
56045
|
"",
|
|
56062
|
-
` ${kleur_default.bold("xtrm install
|
|
56063
|
-
` ${kleur_default.dim("
|
|
56046
|
+
` ${kleur_default.bold("xtrm install")}`,
|
|
56047
|
+
` ${kleur_default.dim("Install plugin + skills + hooks + MCP servers.")}`,
|
|
56064
56048
|
` ${kleur_default.dim("Checks for beads+dolt and prompts to install if missing.")}`,
|
|
56065
56049
|
"",
|
|
56066
|
-
` ${kleur_default.
|
|
56067
|
-
` ${kleur_default.dim("Global install: skills + general hooks + MCP servers.")}`,
|
|
56068
|
-
` ${kleur_default.dim("No beads dependency -- safe to run with zero external deps.")}`,
|
|
56069
|
-
"",
|
|
56070
|
-
` ${kleur_default.bold("xtrm install project")} ${kleur_default.dim("<tool-name | all>")}`,
|
|
56071
|
-
` ${kleur_default.dim("Project-scoped install into .claude/ of current git root.")}`,
|
|
56072
|
-
` ${kleur_default.dim("Run xtrm install project list to see available project skills.")}`,
|
|
56073
|
-
"",
|
|
56074
|
-
` ${kleur_default.dim("Default target directories:")}`,
|
|
56075
|
-
` ${kleur_default.dim("~/.claude/hooks (global hook scripts)")}`,
|
|
56076
|
-
` ${kleur_default.dim("~/.claude/skills (global Claude skills)")}`,
|
|
56077
|
-
` ${kleur_default.dim("~/.agents/skills (agents skills cache mirror)")}`,
|
|
56078
|
-
"",
|
|
56079
|
-
` ${kleur_default.dim("Flags (all profiles): --dry-run --yes / -y --no-mcp --force --prune --backport")}`
|
|
56050
|
+
` ${kleur_default.dim("Flags: --dry-run --yes / -y --no-mcp --force --prune --backport")}`
|
|
56080
56051
|
].join("\n");
|
|
56081
56052
|
const general = HOOK_CATALOG.filter((h) => !h.beads && !h.sessionFlow);
|
|
56082
56053
|
const beads = HOOK_CATALOG.filter((h) => h.beads);
|
|
@@ -56106,23 +56077,14 @@ ${hr}`;
|
|
|
56106
56077
|
"",
|
|
56107
56078
|
skills.length ? skillRows : kleur_default.dim(" (none found -- run from repo root to see skills)")
|
|
56108
56079
|
].join("\n");
|
|
56109
|
-
const psRows = projectSkills.map(
|
|
56110
|
-
(s) => ` ${kleur_default.white(col(s.name, 30))}${kleur_default.dim(s.desc)}`
|
|
56111
|
-
).join("\n");
|
|
56112
|
-
const psSection = [
|
|
56113
|
-
section("PROJECT SKILLS + HOOKS"),
|
|
56114
|
-
"",
|
|
56115
|
-
projectSkills.length ? psRows : kleur_default.dim(" (none found in package)"),
|
|
56116
|
-
"",
|
|
56117
|
-
` ${kleur_default.dim("Install: xtrm install project <name> | xtrm install project list")}`,
|
|
56118
|
-
` ${kleur_default.dim("Each project skill can install .claude/skills plus project hooks/settings.")}`
|
|
56119
|
-
].join("\n");
|
|
56120
56080
|
const otherSection = [
|
|
56121
56081
|
section("OTHER COMMANDS"),
|
|
56122
56082
|
"",
|
|
56123
56083
|
` ${kleur_default.bold("xtrm status")} ${kleur_default.dim("Show pending changes without applying them")}`,
|
|
56124
56084
|
` ${kleur_default.bold("xtrm clean")} ${kleur_default.dim("Remove orphaned hooks and skills not in canonical repo")}`,
|
|
56125
56085
|
` ${kleur_default.bold("xtrm init")} ${kleur_default.dim("Initialize project data (beads, gitnexus, service-registry)")}`,
|
|
56086
|
+
` ${kleur_default.bold("xtrm docs show")} ${kleur_default.dim("Display frontmatter for README, CHANGELOG, docs/*.md")}`,
|
|
56087
|
+
` ${kleur_default.bold("xtrm debug")} ${kleur_default.dim("Watch xtrm hook and bd lifecycle events in real time")}`,
|
|
56126
56088
|
` ${kleur_default.bold("xtrm reset")} ${kleur_default.dim("Clear saved preferences and start fresh")}`,
|
|
56127
56089
|
` ${kleur_default.bold("xtrm end")} ${kleur_default.dim("Close worktree session: rebase, push, PR, link issues, cleanup")}`,
|
|
56128
56090
|
` ${kleur_default.bold("xtrm worktree list")} ${kleur_default.dim("List all active xt/* worktrees with status")}`,
|
|
@@ -56138,7 +56100,7 @@ ${hr}`;
|
|
|
56138
56100
|
` ${kleur_default.dim("Run 'xtrm <command> --help' for command-specific options.")}`,
|
|
56139
56101
|
""
|
|
56140
56102
|
].join("\n");
|
|
56141
|
-
console.log([installSection, hooksSection, skillsSection,
|
|
56103
|
+
console.log([installSection, hooksSection, skillsSection, otherSection, resourcesSection].join("\n"));
|
|
56142
56104
|
});
|
|
56143
56105
|
}
|
|
56144
56106
|
|
|
@@ -56908,30 +56870,66 @@ var import_node_child_process8 = require("child_process");
|
|
|
56908
56870
|
var import_node_fs5 = require("fs");
|
|
56909
56871
|
var import_node_path6 = require("path");
|
|
56910
56872
|
var KIND_LABELS = {
|
|
56911
|
-
"
|
|
56912
|
-
"
|
|
56913
|
-
"
|
|
56914
|
-
"
|
|
56915
|
-
"
|
|
56916
|
-
"
|
|
56917
|
-
"
|
|
56918
|
-
"
|
|
56919
|
-
"
|
|
56873
|
+
"session.start": { label: "SESS+", color: kleur_default.green },
|
|
56874
|
+
"session.end": { label: "SESS-", color: kleur_default.dim },
|
|
56875
|
+
"gate.edit.allow": { label: "EDIT+", color: kleur_default.green },
|
|
56876
|
+
"gate.edit.block": { label: "EDIT-", color: kleur_default.red },
|
|
56877
|
+
"gate.commit.allow": { label: "CMIT+", color: kleur_default.green },
|
|
56878
|
+
"gate.commit.block": { label: "CMIT-", color: kleur_default.red },
|
|
56879
|
+
"gate.stop.block": { label: "STOP-", color: kleur_default.red },
|
|
56880
|
+
"gate.memory.triggered": { label: "MEMO-", color: kleur_default.yellow },
|
|
56881
|
+
"gate.memory.acked": { label: "MEMO+", color: kleur_default.green },
|
|
56882
|
+
"gate.worktree.block": { label: "WTRE-", color: kleur_default.red },
|
|
56920
56883
|
"bd.claimed": { label: "CLMD ", color: kleur_default.cyan },
|
|
56921
56884
|
"bd.closed": { label: "CLSD ", color: kleur_default.green },
|
|
56922
|
-
"bd.
|
|
56885
|
+
"bd.committed": {
|
|
56886
|
+
label: (outcome) => outcome === "error" ? "ACMT-" : "ACMT+",
|
|
56887
|
+
color: (outcome) => outcome === "error" ? kleur_default.red : kleur_default.cyan
|
|
56888
|
+
}
|
|
56889
|
+
};
|
|
56890
|
+
var TOOL_ABBREVS = {
|
|
56891
|
+
Bash: "BASH",
|
|
56892
|
+
bash: "BASH",
|
|
56893
|
+
execute_shell_command: "BASH",
|
|
56894
|
+
Read: "READ",
|
|
56895
|
+
Write: "WRIT",
|
|
56896
|
+
Edit: "EDIT",
|
|
56897
|
+
MultiEdit: "EDIT",
|
|
56898
|
+
NotebookEdit: "NTED",
|
|
56899
|
+
Glob: "GLOB",
|
|
56900
|
+
Grep: "GREP",
|
|
56901
|
+
WebFetch: "WBFT",
|
|
56902
|
+
WebSearch: "WSRC",
|
|
56903
|
+
Agent: "AGNT",
|
|
56904
|
+
Task: "TASK",
|
|
56905
|
+
LSP: "LSP "
|
|
56923
56906
|
};
|
|
56924
|
-
function
|
|
56925
|
-
|
|
56907
|
+
function toolAbbrev(toolName) {
|
|
56908
|
+
if (TOOL_ABBREVS[toolName]) return TOOL_ABBREVS[toolName];
|
|
56909
|
+
if (toolName.startsWith("mcp__serena__")) return "SRNA";
|
|
56910
|
+
if (toolName.startsWith("mcp__gitnexus__")) return "GTNX";
|
|
56911
|
+
if (toolName.startsWith("mcp__deepwiki__")) return "WIKI";
|
|
56912
|
+
if (toolName.startsWith("mcp__")) return "MCP ";
|
|
56913
|
+
return toolName.slice(0, 4).toUpperCase();
|
|
56914
|
+
}
|
|
56915
|
+
function getLabel(event) {
|
|
56916
|
+
if (event.kind === "tool.call") {
|
|
56917
|
+
const abbrev = toolAbbrev(event.tool_name ?? "").padEnd(5);
|
|
56918
|
+
return event.outcome === "error" ? kleur_default.red(abbrev) : kleur_default.dim(abbrev);
|
|
56919
|
+
}
|
|
56920
|
+
const def = KIND_LABELS[event.kind];
|
|
56926
56921
|
if (!def) {
|
|
56927
|
-
const
|
|
56928
|
-
const label = `${
|
|
56929
|
-
return outcome === "block" ? kleur_default.red(label) : kleur_default.
|
|
56922
|
+
const seg = (event.kind.split(".").pop() ?? "UNKN").slice(0, 4).toUpperCase();
|
|
56923
|
+
const label = `${seg}${event.outcome === "block" ? "-" : "+"}`.padEnd(5);
|
|
56924
|
+
return event.outcome === "block" ? kleur_default.red(label) : kleur_default.dim(label);
|
|
56930
56925
|
}
|
|
56931
|
-
if (kind === "bd.
|
|
56932
|
-
|
|
56926
|
+
if (event.kind === "bd.committed") {
|
|
56927
|
+
const label = event.outcome === "error" ? "ACMT-" : "ACMT+";
|
|
56928
|
+
return event.outcome === "error" ? kleur_default.red(label) : kleur_default.cyan(label);
|
|
56933
56929
|
}
|
|
56934
|
-
return def.color(
|
|
56930
|
+
return def.color(
|
|
56931
|
+
def.label
|
|
56932
|
+
);
|
|
56935
56933
|
}
|
|
56936
56934
|
var SESSION_COLORS = [
|
|
56937
56935
|
kleur_default.blue,
|
|
@@ -56940,7 +56938,7 @@ var SESSION_COLORS = [
|
|
|
56940
56938
|
kleur_default.cyan,
|
|
56941
56939
|
kleur_default.magenta
|
|
56942
56940
|
];
|
|
56943
|
-
function
|
|
56941
|
+
function buildColorMap(events) {
|
|
56944
56942
|
const map2 = /* @__PURE__ */ new Map();
|
|
56945
56943
|
for (const ev of events) {
|
|
56946
56944
|
if (!map2.has(ev.session_id)) {
|
|
@@ -56949,146 +56947,112 @@ function buildSessionColorMap(events) {
|
|
|
56949
56947
|
}
|
|
56950
56948
|
return map2;
|
|
56951
56949
|
}
|
|
56952
|
-
function
|
|
56950
|
+
function extendColorMap(map2, events) {
|
|
56953
56951
|
for (const ev of events) {
|
|
56954
56952
|
if (!map2.has(ev.session_id)) {
|
|
56955
56953
|
map2.set(ev.session_id, SESSION_COLORS[map2.size % SESSION_COLORS.length]);
|
|
56956
56954
|
}
|
|
56957
56955
|
}
|
|
56958
56956
|
}
|
|
56959
|
-
function fmtTime(
|
|
56960
|
-
|
|
56961
|
-
const d = new Date(created_at);
|
|
56962
|
-
if (isNaN(d.getTime())) return created_at.slice(11, 19) || "??:??:??";
|
|
56963
|
-
return d.toLocaleTimeString("en-GB", { hour12: false });
|
|
56964
|
-
} catch {
|
|
56965
|
-
return created_at.slice(11, 19) || "??:??:??";
|
|
56966
|
-
}
|
|
56957
|
+
function fmtTime(ts) {
|
|
56958
|
+
return new Date(ts).toLocaleTimeString("en-GB", { hour12: false });
|
|
56967
56959
|
}
|
|
56968
56960
|
function buildDetail(event) {
|
|
56969
56961
|
const parts = [];
|
|
56970
|
-
|
|
56971
|
-
if (event.
|
|
56972
|
-
|
|
56962
|
+
let d = null;
|
|
56963
|
+
if (event.data) {
|
|
56964
|
+
try {
|
|
56965
|
+
d = JSON.parse(event.data);
|
|
56966
|
+
} catch {
|
|
56967
|
+
}
|
|
56968
|
+
}
|
|
56969
|
+
if (event.kind === "tool.call") {
|
|
56970
|
+
if (d?.cmd) parts.push(kleur_default.dim(d.cmd.slice(0, 60)));
|
|
56971
|
+
if (d?.file) parts.push(kleur_default.dim(d.file));
|
|
56972
|
+
if (d?.pattern) parts.push(kleur_default.dim(`/${d.pattern}/`));
|
|
56973
|
+
if (d?.url) parts.push(kleur_default.dim(d.url.slice(0, 60)));
|
|
56974
|
+
if (d?.query) parts.push(kleur_default.dim(d.query.slice(0, 60)));
|
|
56975
|
+
if (d?.prompt) parts.push(kleur_default.dim(d.prompt.slice(0, 60)));
|
|
56976
|
+
} else {
|
|
56977
|
+
if (event.issue_id) parts.push(kleur_default.yellow(event.issue_id));
|
|
56978
|
+
if (d?.file) parts.push(kleur_default.dim(d.file));
|
|
56979
|
+
if (d?.msg) parts.push(kleur_default.dim(d.msg.slice(0, 60)));
|
|
56980
|
+
if (d?.reason_code) parts.push(kleur_default.dim(`[${d.reason_code}]`));
|
|
56981
|
+
if (event.worktree) parts.push(kleur_default.dim(`wt:${event.worktree}`));
|
|
56982
|
+
}
|
|
56973
56983
|
return parts.join(" ") || kleur_default.dim("\u2014");
|
|
56974
56984
|
}
|
|
56975
|
-
function
|
|
56976
|
-
const time3 = kleur_default.dim(fmtTime(event.
|
|
56985
|
+
function formatLine(event, colorMap) {
|
|
56986
|
+
const time3 = kleur_default.dim(fmtTime(event.ts));
|
|
56977
56987
|
const colorFn = colorMap.get(event.session_id) ?? kleur_default.white;
|
|
56978
56988
|
const session = colorFn(event.session_id.slice(0, 8));
|
|
56979
|
-
const label = getLabel(event
|
|
56989
|
+
const label = getLabel(event);
|
|
56980
56990
|
const detail = buildDetail(event);
|
|
56981
56991
|
return `${time3} ${session} ${label} ${detail}`;
|
|
56982
56992
|
}
|
|
56983
56993
|
function printHeader() {
|
|
56984
|
-
|
|
56985
|
-
|
|
56994
|
+
console.log(
|
|
56995
|
+
` ${kleur_default.dim("TIME ")} ${kleur_default.dim("SESSION ")} ${kleur_default.dim("LABEL")} ${kleur_default.dim("DETAIL")}`
|
|
56996
|
+
);
|
|
56986
56997
|
console.log(` ${kleur_default.dim("\u2500".repeat(72))}`);
|
|
56987
56998
|
}
|
|
56988
|
-
function
|
|
56999
|
+
function findDbPath(cwd) {
|
|
56989
57000
|
let dir = cwd;
|
|
56990
57001
|
for (let i = 0; i < 10; i++) {
|
|
56991
|
-
if ((0, import_node_fs5.existsSync)((0, import_node_path6.join)(dir, ".beads"))) return dir;
|
|
57002
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path6.join)(dir, ".beads"))) return (0, import_node_path6.join)(dir, ".xtrm", "debug.db");
|
|
56992
57003
|
const parent = (0, import_node_path6.join)(dir, "..");
|
|
56993
57004
|
if (parent === dir) break;
|
|
56994
57005
|
dir = parent;
|
|
56995
57006
|
}
|
|
56996
57007
|
return null;
|
|
56997
57008
|
}
|
|
56998
|
-
function
|
|
56999
|
-
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
57000
|
-
const sepIdx = lines.findIndex((l) => /^[-+\s]+$/.test(l) && l.includes("-"));
|
|
57001
|
-
if (sepIdx < 1) return [];
|
|
57002
|
-
return lines.slice(sepIdx + 1).filter((l) => !l.startsWith("(")).map((line) => {
|
|
57003
|
-
const cells = line.split("|").map((c) => c.trim());
|
|
57004
|
-
const row = {};
|
|
57005
|
-
columns2.forEach((col2, i) => {
|
|
57006
|
-
row[col2] = cells[i] ?? "";
|
|
57007
|
-
});
|
|
57008
|
-
return row;
|
|
57009
|
-
});
|
|
57010
|
-
}
|
|
57011
|
-
var COLS = "seq, id, created_at, runtime, session_id, worktree, layer, kind, outcome, tool_name, issue_id";
|
|
57012
|
-
var COL_NAMES = ["seq", "id", "created_at", "runtime", "session_id", "worktree", "layer", "kind", "outcome", "tool_name", "issue_id"];
|
|
57013
|
-
var ADD_SEQ_SQL = `ALTER TABLE xtrm_events ADD COLUMN seq INT NOT NULL AUTO_INCREMENT, ADD UNIQUE KEY uk_seq (seq)`;
|
|
57014
|
-
function queryEvents(cwd, where, limit) {
|
|
57015
|
-
const sql = `SELECT ${COLS} FROM xtrm_events${where ? ` WHERE ${where}` : ""} ORDER BY seq ASC LIMIT ${limit}`;
|
|
57016
|
-
let result = (0, import_node_child_process8.spawnSync)("bd", ["sql", sql], {
|
|
57017
|
-
cwd,
|
|
57018
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
57019
|
-
encoding: "utf8",
|
|
57020
|
-
timeout: 8e3
|
|
57021
|
-
});
|
|
57022
|
-
if (result.status !== 0) {
|
|
57023
|
-
(0, import_node_child_process8.spawnSync)("bd", ["sql", ADD_SEQ_SQL], {
|
|
57024
|
-
cwd,
|
|
57025
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
57026
|
-
encoding: "utf8",
|
|
57027
|
-
timeout: 5e3
|
|
57028
|
-
});
|
|
57029
|
-
result = (0, import_node_child_process8.spawnSync)("bd", ["sql", sql], {
|
|
57030
|
-
cwd,
|
|
57031
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
57032
|
-
encoding: "utf8",
|
|
57033
|
-
timeout: 8e3
|
|
57034
|
-
});
|
|
57035
|
-
}
|
|
57036
|
-
if (result.status !== 0) return [];
|
|
57037
|
-
return parseBdTable(result.stdout, COL_NAMES).map((r) => ({
|
|
57038
|
-
seq: parseInt(r.seq, 10) || 0,
|
|
57039
|
-
id: r.id,
|
|
57040
|
-
created_at: r.created_at,
|
|
57041
|
-
runtime: r.runtime,
|
|
57042
|
-
session_id: r.session_id,
|
|
57043
|
-
worktree: r.worktree === "<nil>" || !r.worktree ? null : r.worktree,
|
|
57044
|
-
layer: r.layer,
|
|
57045
|
-
kind: r.kind,
|
|
57046
|
-
outcome: r.outcome,
|
|
57047
|
-
tool_name: r.tool_name === "<nil>" || !r.tool_name ? null : r.tool_name,
|
|
57048
|
-
issue_id: r.issue_id === "<nil>" || !r.issue_id ? null : r.issue_id
|
|
57049
|
-
}));
|
|
57050
|
-
}
|
|
57051
|
-
function buildWhere(opts, baseClause) {
|
|
57009
|
+
function buildWhere(opts, base) {
|
|
57052
57010
|
const clauses = [];
|
|
57053
|
-
if (
|
|
57011
|
+
if (base) clauses.push(base);
|
|
57054
57012
|
if (opts.session) {
|
|
57055
57013
|
const s = opts.session.replace(/'/g, "''");
|
|
57056
57014
|
clauses.push(`session_id LIKE '${s}%'`);
|
|
57057
57015
|
}
|
|
57058
57016
|
if (opts.type) {
|
|
57059
|
-
const
|
|
57060
|
-
clauses.push(`
|
|
57017
|
+
const t3 = opts.type.replace(/'/g, "''");
|
|
57018
|
+
clauses.push(`kind LIKE '${t3}.%' OR kind = '${t3}'`);
|
|
57019
|
+
}
|
|
57020
|
+
return clauses.length ? clauses.join(" AND ") : "";
|
|
57021
|
+
}
|
|
57022
|
+
function queryEvents(dbPath, where, limit) {
|
|
57023
|
+
const sql = `SELECT id,ts,session_id,runtime,worktree,kind,tool_name,outcome,issue_id,duration_ms,data FROM events${where ? ` WHERE ${where}` : ""} ORDER BY id ASC LIMIT ${limit}`;
|
|
57024
|
+
const result = (0, import_node_child_process8.spawnSync)("sqlite3", [dbPath, "-json", sql], {
|
|
57025
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57026
|
+
encoding: "utf8",
|
|
57027
|
+
timeout: 5e3
|
|
57028
|
+
});
|
|
57029
|
+
if (result.status !== 0 || !result.stdout.trim()) return [];
|
|
57030
|
+
try {
|
|
57031
|
+
return JSON.parse(result.stdout);
|
|
57032
|
+
} catch {
|
|
57033
|
+
return [];
|
|
57061
57034
|
}
|
|
57062
|
-
return clauses.join(" AND ");
|
|
57063
57035
|
}
|
|
57064
|
-
function follow(
|
|
57065
|
-
const
|
|
57066
|
-
const initial = queryEvents(
|
|
57067
|
-
const colorMap =
|
|
57068
|
-
let
|
|
57036
|
+
function follow(dbPath, opts) {
|
|
57037
|
+
const sinceTs = Date.now() - 5 * 60 * 1e3;
|
|
57038
|
+
const initial = queryEvents(dbPath, buildWhere(opts, `ts >= ${sinceTs}`), 200);
|
|
57039
|
+
const colorMap = buildColorMap(initial);
|
|
57040
|
+
let lastId = 0;
|
|
57069
57041
|
if (initial.length > 0) {
|
|
57070
57042
|
for (const ev of initial) {
|
|
57071
|
-
if (ev.
|
|
57072
|
-
|
|
57073
|
-
console.log(JSON.stringify(ev));
|
|
57074
|
-
} else {
|
|
57075
|
-
console.log(" " + formatEventLine(ev, colorMap));
|
|
57076
|
-
}
|
|
57043
|
+
if (ev.id > lastId) lastId = ev.id;
|
|
57044
|
+
opts.json ? console.log(JSON.stringify(ev)) : console.log(" " + formatLine(ev, colorMap));
|
|
57077
57045
|
}
|
|
57078
57046
|
} else if (!opts.json) {
|
|
57079
57047
|
console.log(kleur_default.dim(" (no recent events \u2014 waiting for new ones)\n"));
|
|
57080
57048
|
}
|
|
57081
57049
|
const interval = setInterval(() => {
|
|
57082
|
-
const events = queryEvents(
|
|
57050
|
+
const events = queryEvents(dbPath, buildWhere(opts, `id > ${lastId}`), 50);
|
|
57083
57051
|
if (events.length > 0) {
|
|
57084
|
-
|
|
57052
|
+
extendColorMap(colorMap, events);
|
|
57085
57053
|
for (const ev of events) {
|
|
57086
|
-
if (ev.
|
|
57087
|
-
|
|
57088
|
-
console.log(JSON.stringify(ev));
|
|
57089
|
-
} else {
|
|
57090
|
-
console.log(" " + formatEventLine(ev, colorMap));
|
|
57091
|
-
}
|
|
57054
|
+
if (ev.id > lastId) lastId = ev.id;
|
|
57055
|
+
opts.json ? console.log(JSON.stringify(ev)) : console.log(" " + formatLine(ev, colorMap));
|
|
57092
57056
|
}
|
|
57093
57057
|
}
|
|
57094
57058
|
}, 2e3);
|
|
@@ -57099,44 +57063,36 @@ function follow(cwd, opts) {
|
|
|
57099
57063
|
});
|
|
57100
57064
|
}
|
|
57101
57065
|
function createDebugCommand() {
|
|
57102
|
-
return new Command("debug").description("Watch xtrm
|
|
57066
|
+
return new Command("debug").description("Watch xtrm events: tool calls, gate decisions, bd lifecycle").option("-f, --follow", "Follow new events (default)", false).option("--all", "Show full history and exit", false).option("--session <id>", "Filter by session ID (prefix match)").option("--type <domain>", "Filter by domain: tool | gate | bd | session").option("--json", "Output raw JSON lines", false).action((opts) => {
|
|
57103
57067
|
const cwd = process.cwd();
|
|
57104
|
-
const
|
|
57105
|
-
if (!
|
|
57106
|
-
|
|
57107
|
-
|
|
57108
|
-
|
|
57068
|
+
const dbPath = findDbPath(cwd);
|
|
57069
|
+
if (!dbPath || !(0, import_node_fs5.existsSync)(dbPath)) {
|
|
57070
|
+
if (!opts.json) {
|
|
57071
|
+
console.log(kleur_default.bold("\n xtrm event log"));
|
|
57072
|
+
console.log(kleur_default.dim(" No events yet \u2014 DB initializes on first hook fire.\n"));
|
|
57073
|
+
console.log(kleur_default.dim(" Run from inside an xtrm project with beads initialized.\n"));
|
|
57074
|
+
}
|
|
57075
|
+
return;
|
|
57109
57076
|
}
|
|
57110
57077
|
if (!opts.json) {
|
|
57111
57078
|
console.log(kleur_default.bold("\n xtrm event log"));
|
|
57112
|
-
|
|
57113
|
-
console.log(kleur_default.dim(" Showing full history\n"));
|
|
57114
|
-
} else {
|
|
57115
|
-
console.log(kleur_default.dim(" Following events \u2014 Ctrl+C to stop\n"));
|
|
57116
|
-
}
|
|
57079
|
+
console.log(kleur_default.dim(opts.all ? " Full history\n" : " Following \u2014 Ctrl+C to stop\n"));
|
|
57117
57080
|
printHeader();
|
|
57118
57081
|
}
|
|
57119
57082
|
if (opts.all) {
|
|
57120
|
-
const events = queryEvents(
|
|
57083
|
+
const events = queryEvents(dbPath, buildWhere(opts, ""), 1e3);
|
|
57121
57084
|
if (events.length === 0) {
|
|
57122
|
-
if (!opts.json)
|
|
57123
|
-
console.log(kleur_default.dim("\n No events recorded yet."));
|
|
57124
|
-
console.log(kleur_default.dim(" Events appear here as hooks fire and bd lifecycle runs.\n"));
|
|
57125
|
-
}
|
|
57085
|
+
if (!opts.json) console.log(kleur_default.dim("\n No events recorded yet.\n"));
|
|
57126
57086
|
} else {
|
|
57127
|
-
const colorMap =
|
|
57087
|
+
const colorMap = buildColorMap(events);
|
|
57128
57088
|
for (const ev of events) {
|
|
57129
|
-
|
|
57130
|
-
console.log(JSON.stringify(ev));
|
|
57131
|
-
} else {
|
|
57132
|
-
console.log(" " + formatEventLine(ev, colorMap));
|
|
57133
|
-
}
|
|
57089
|
+
opts.json ? console.log(JSON.stringify(ev)) : console.log(" " + formatLine(ev, colorMap));
|
|
57134
57090
|
}
|
|
57135
57091
|
if (!opts.json) console.log("");
|
|
57136
57092
|
}
|
|
57137
57093
|
return;
|
|
57138
57094
|
}
|
|
57139
|
-
follow(
|
|
57095
|
+
follow(dbPath, opts);
|
|
57140
57096
|
});
|
|
57141
57097
|
}
|
|
57142
57098
|
|