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 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.10** | [Complete Guide](XTRM-GUIDE.md) | [Changelog](CHANGELOG.md)
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
- | `branch-state.json` | claude | Branch context injection |
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
- | `serena.json` | claude | Serena LSP workflow reminder |
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 |
@@ -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 = plugins?.plugins?.["xtrm-tools@xtrm-tools"];
41307
- if (entries?.length > 0) {
41308
- return import_node_path5.default.join(entries[0].installPath, "hooks", "statusline.mjs");
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 all")}`,
56063
- ` ${kleur_default.dim("Global install: skills + all hooks (including beads gates) + MCP servers.")}`,
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.bold("xtrm install basic")}`,
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, psSection, otherSection, resourcesSection].join("\n"));
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
- "hook.edit_gate.allow": { label: "EDIT+", color: kleur_default.green },
56912
- "hook.edit_gate.block": { label: "EDIT-", color: kleur_default.red },
56913
- "hook.commit_gate.allow": { label: "CMIT+", color: kleur_default.green },
56914
- "hook.commit_gate.block": { label: "CMIT-", color: kleur_default.red },
56915
- "hook.stop_gate.allow": { label: "STOP+", color: kleur_default.green },
56916
- "hook.stop_gate.block": { label: "STOP-", color: kleur_default.red },
56917
- "hook.memory_gate.acked": { label: "MEMO+", color: kleur_default.green },
56918
- "hook.memory_gate.triggered": { label: "MEMO-", color: kleur_default.yellow },
56919
- "hook.worktree_boundary.block": { label: "WTRE-", color: kleur_default.red },
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.auto_committed": { label: "ACMT+", color: kleur_default.cyan }
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 getLabel(kind, outcome) {
56925
- const def = KIND_LABELS[kind];
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 short = (kind.split(".").pop() ?? "UNKN").slice(0, 4).toUpperCase();
56928
- const label = `${short}${outcome === "block" ? "-" : "+"}`.padEnd(5);
56929
- return outcome === "block" ? kleur_default.red(label) : kleur_default.green(label);
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.auto_committed") {
56932
- return outcome === "block" ? kleur_default.red("ACMT-") : kleur_default.cyan("ACMT+");
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(def.label);
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 buildSessionColorMap(events) {
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 extendSessionColorMap(map2, events) {
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(created_at) {
56960
- try {
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
- if (event.tool_name) parts.push(kleur_default.dim(`tool=${event.tool_name}`));
56971
- if (event.issue_id) parts.push(kleur_default.yellow(`issue=${event.issue_id}`));
56972
- if (event.worktree) parts.push(kleur_default.dim(`wt=${event.worktree}`));
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 formatEventLine(event, colorMap) {
56976
- const time3 = kleur_default.dim(fmtTime(event.created_at));
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.kind, event.outcome);
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
- const h = kleur_default.dim;
56985
- console.log(` ${h("TIME ")} ${h("SESSION ")} ${h("LABEL")} ${h("DETAIL")}`);
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 findProjectRoot(cwd) {
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 parseBdTable(output, columns2) {
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 (baseClause) clauses.push(baseClause);
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 layer = opts.type.replace(/'/g, "''");
57060
- clauses.push(`layer = '${layer}'`);
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(cwd, opts) {
57065
- const fiveMinsAgo = new Date(Date.now() - 5 * 60 * 1e3).toISOString().replace("T", " ").slice(0, 19);
57066
- const initial = queryEvents(cwd, buildWhere(opts, `created_at >= '${fiveMinsAgo}'`), 200);
57067
- const colorMap = buildSessionColorMap(initial);
57068
- let lastSeq = 0;
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.seq > lastSeq) lastSeq = ev.seq;
57072
- if (opts.json) {
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(cwd, buildWhere(opts, `seq > ${lastSeq}`), 50);
57050
+ const events = queryEvents(dbPath, buildWhere(opts, `id > ${lastId}`), 50);
57083
57051
  if (events.length > 0) {
57084
- extendSessionColorMap(colorMap, events);
57052
+ extendColorMap(colorMap, events);
57085
57053
  for (const ev of events) {
57086
- if (ev.seq > lastSeq) lastSeq = ev.seq;
57087
- if (opts.json) {
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 hook and bd lifecycle events in real time").option("-f, --follow", "Follow new events (default when no other mode set)", false).option("--all", "Show full history and exit", false).option("--session <id>", "Filter by session ID (prefix match)").option("--type <layer>", "Filter by layer: gate | bd").option("--json", "Output raw JSON lines", false).action((opts) => {
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 root = findProjectRoot(cwd);
57105
- if (!root) {
57106
- console.error(kleur_default.red("\n \u2717 No beads project found (.beads directory missing)\n"));
57107
- console.error(kleur_default.dim(" Run from inside an xtrm project.\n"));
57108
- process.exit(1);
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
- if (opts.all) {
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(root, buildWhere(opts, ""), 200);
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 = buildSessionColorMap(events);
57087
+ const colorMap = buildColorMap(events);
57128
57088
  for (const ev of events) {
57129
- if (opts.json) {
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(root, opts);
57095
+ follow(dbPath, opts);
57140
57096
  });
57141
57097
  }
57142
57098