xtrm-tools 0.5.23 → 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
 
@@ -56907,143 +56869,190 @@ function createDocsCommand() {
56907
56869
  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
- function findProjectRoot(cwd) {
56911
- let dir = cwd;
56912
- for (let i = 0; i < 10; i++) {
56913
- if ((0, import_node_fs5.existsSync)((0, import_node_path6.join)(dir, ".beads"))) return dir;
56914
- const parent = (0, import_node_path6.join)(dir, "..");
56915
- if (parent === dir) break;
56916
- dir = parent;
56872
+ var KIND_LABELS = {
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 },
56883
+ "bd.claimed": { label: "CLMD ", color: kleur_default.cyan },
56884
+ "bd.closed": { label: "CLSD ", color: kleur_default.green },
56885
+ "bd.committed": {
56886
+ label: (outcome) => outcome === "error" ? "ACMT-" : "ACMT+",
56887
+ color: (outcome) => outcome === "error" ? kleur_default.red : kleur_default.cyan
56917
56888
  }
56918
- return null;
56919
- }
56920
- function parseBdTable(output, columns2) {
56921
- const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
56922
- const sepIdx = lines.findIndex((l) => /^[-+\s]+$/.test(l) && l.includes("-"));
56923
- if (sepIdx < 1) return [];
56924
- const dataLines = lines.slice(sepIdx + 1).filter((l) => !l.startsWith("("));
56925
- return dataLines.map((line) => {
56926
- const cells = line.split("|").map((c) => c.trim());
56927
- const row = {};
56928
- columns2.forEach((col2, i) => {
56929
- row[col2] = cells[i] ?? "";
56930
- });
56931
- return row;
56932
- });
56933
- }
56934
- function queryEvents(cwd, whereExtra, limit) {
56935
- const cols = "id, created_at, runtime, session_id, worktree, layer, kind, outcome, tool_name, issue_id";
56936
- const colNames = ["id", "created_at", "runtime", "session_id", "worktree", "layer", "kind", "outcome", "tool_name", "issue_id"];
56937
- const where = whereExtra ? `WHERE ${whereExtra}` : "";
56938
- const sql = `SELECT ${cols} FROM xtrm_events ${where} ORDER BY created_at ASC, id ASC LIMIT ${limit}`;
56939
- const result = (0, import_node_child_process8.spawnSync)("bd", ["sql", sql], {
56940
- cwd,
56941
- stdio: ["pipe", "pipe", "pipe"],
56942
- encoding: "utf8",
56943
- timeout: 8e3
56944
- });
56945
- if (result.status !== 0) return [];
56946
- const rows = parseBdTable(result.stdout, colNames);
56947
- return rows.map((r) => ({
56948
- id: r.id,
56949
- created_at: r.created_at,
56950
- runtime: r.runtime,
56951
- session_id: r.session_id,
56952
- worktree: r.worktree === "<nil>" || !r.worktree ? null : r.worktree,
56953
- layer: r.layer,
56954
- kind: r.kind,
56955
- outcome: r.outcome,
56956
- tool_name: r.tool_name === "<nil>" || !r.tool_name ? null : r.tool_name,
56957
- issue_id: r.issue_id === "<nil>" || !r.issue_id ? null : r.issue_id
56958
- }));
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 "
56906
+ };
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];
56921
+ if (!def) {
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);
56925
+ }
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);
56929
+ }
56930
+ return def.color(
56931
+ def.label
56932
+ );
56959
56933
  }
56960
- function fmtTime(created_at) {
56961
- try {
56962
- const d = new Date(created_at);
56963
- if (isNaN(d.getTime())) return created_at.slice(11, 19) || "??:??:??";
56964
- return d.toLocaleTimeString("en-GB", { hour12: false });
56965
- } catch {
56966
- return created_at.slice(11, 19) || "??:??:??";
56934
+ var SESSION_COLORS = [
56935
+ kleur_default.blue,
56936
+ kleur_default.green,
56937
+ kleur_default.yellow,
56938
+ kleur_default.cyan,
56939
+ kleur_default.magenta
56940
+ ];
56941
+ function buildColorMap(events) {
56942
+ const map2 = /* @__PURE__ */ new Map();
56943
+ for (const ev of events) {
56944
+ if (!map2.has(ev.session_id)) {
56945
+ map2.set(ev.session_id, SESSION_COLORS[map2.size % SESSION_COLORS.length]);
56946
+ }
56967
56947
  }
56948
+ return map2;
56968
56949
  }
56969
- function fmtKind(kind, outcome) {
56970
- const dot = outcome === "block" ? kleur_default.red("\u25CF") : kleur_default.green("\u25CB");
56971
- const label = kind.padEnd(36);
56972
- const colored = outcome === "block" ? kleur_default.red(label) : kleur_default.green(label);
56973
- return `${dot} ${colored}`;
56950
+ function extendColorMap(map2, events) {
56951
+ for (const ev of events) {
56952
+ if (!map2.has(ev.session_id)) {
56953
+ map2.set(ev.session_id, SESSION_COLORS[map2.size % SESSION_COLORS.length]);
56954
+ }
56955
+ }
56974
56956
  }
56975
- function fmtSession(sessionId) {
56976
- const short = sessionId.length > 8 ? sessionId.slice(0, 8) : sessionId;
56977
- return kleur_default.dim(short);
56957
+ function fmtTime(ts) {
56958
+ return new Date(ts).toLocaleTimeString("en-GB", { hour12: false });
56978
56959
  }
56979
- function fmtMeta(event) {
56960
+ function buildDetail(event) {
56980
56961
  const parts = [];
56981
- if (event.tool_name) parts.push(kleur_default.cyan(event.tool_name.padEnd(10)));
56982
- if (event.issue_id) parts.push(kleur_default.yellow(event.issue_id));
56983
- if (event.worktree) parts.push(kleur_default.dim(`[${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
+ }
56984
56983
  return parts.join(" ") || kleur_default.dim("\u2014");
56985
56984
  }
56986
- function printEvent(event) {
56987
- const time3 = kleur_default.dim(`[${fmtTime(event.created_at)}]`);
56988
- const session = fmtSession(event.session_id);
56989
- const kind = fmtKind(event.kind, event.outcome);
56990
- const meta3 = fmtMeta(event);
56991
- console.log(` ${time3} ${session} ${kind} ${meta3}`);
56985
+ function formatLine(event, colorMap) {
56986
+ const time3 = kleur_default.dim(fmtTime(event.ts));
56987
+ const colorFn = colorMap.get(event.session_id) ?? kleur_default.white;
56988
+ const session = colorFn(event.session_id.slice(0, 8));
56989
+ const label = getLabel(event);
56990
+ const detail = buildDetail(event);
56991
+ return `${time3} ${session} ${label} ${detail}`;
56992
56992
  }
56993
56993
  function printHeader() {
56994
- const h = kleur_default.dim;
56995
- const col2 = (s, w) => s.padEnd(w);
56996
56994
  console.log(
56997
- ` ${h(col2("[TIME]", 10))} ${h(col2("SESSION", 10))} ${h("\u25CF/\u25CB")} ${h(col2("KIND", 37))} ${h("TOOL/ISSUE")}`
56995
+ ` ${kleur_default.dim("TIME ")} ${kleur_default.dim("SESSION ")} ${kleur_default.dim("LABEL")} ${kleur_default.dim("DETAIL")}`
56998
56996
  );
56999
- console.log(` ${kleur_default.dim("\u2500".repeat(90))}`);
56997
+ console.log(` ${kleur_default.dim("\u2500".repeat(72))}`);
57000
56998
  }
57001
- function buildWhereClause(opts, sinceTs) {
57002
- const clauses = [];
57003
- if (sinceTs) {
57004
- clauses.push(`created_at >= '${sinceTs}'`);
56999
+ function findDbPath(cwd) {
57000
+ let dir = cwd;
57001
+ for (let i = 0; i < 10; i++) {
57002
+ if ((0, import_node_fs5.existsSync)((0, import_node_path6.join)(dir, ".beads"))) return (0, import_node_path6.join)(dir, ".xtrm", "debug.db");
57003
+ const parent = (0, import_node_path6.join)(dir, "..");
57004
+ if (parent === dir) break;
57005
+ dir = parent;
57005
57006
  }
57007
+ return null;
57008
+ }
57009
+ function buildWhere(opts, base) {
57010
+ const clauses = [];
57011
+ if (base) clauses.push(base);
57006
57012
  if (opts.session) {
57007
57013
  const s = opts.session.replace(/'/g, "''");
57008
57014
  clauses.push(`session_id LIKE '${s}%'`);
57009
57015
  }
57010
57016
  if (opts.type) {
57011
- const layer = opts.type.replace(/'/g, "''");
57012
- clauses.push(`layer = '${layer}'`);
57017
+ const t3 = opts.type.replace(/'/g, "''");
57018
+ clauses.push(`kind LIKE '${t3}.%' OR kind = '${t3}'`);
57013
57019
  }
57014
- return clauses.join(" AND ");
57020
+ return clauses.length ? clauses.join(" AND ") : "";
57015
57021
  }
57016
- function watch(cwd, opts) {
57017
- const seenIds = /* @__PURE__ */ new Set();
57018
- let lastTs = null;
57019
- const fiveMinsAgo = new Date(Date.now() - 5 * 60 * 1e3).toISOString().replace("T", " ").slice(0, 19);
57020
- const initialWhere = buildWhereClause(opts, fiveMinsAgo);
57021
- const initial = queryEvents(cwd, initialWhere, 200);
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 [];
57034
+ }
57035
+ }
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;
57022
57041
  if (initial.length > 0) {
57023
57042
  for (const ev of initial) {
57024
- seenIds.add(ev.id);
57025
- if (!lastTs || ev.created_at > lastTs) lastTs = ev.created_at;
57026
- if (opts.json) {
57027
- console.log(JSON.stringify(ev));
57028
- } else {
57029
- printEvent(ev);
57030
- }
57043
+ if (ev.id > lastId) lastId = ev.id;
57044
+ opts.json ? console.log(JSON.stringify(ev)) : console.log(" " + formatLine(ev, colorMap));
57031
57045
  }
57032
- } else {
57033
- if (!opts.json) console.log(kleur_default.dim(" (no recent events \u2014 waiting for new ones)\n"));
57046
+ } else if (!opts.json) {
57047
+ console.log(kleur_default.dim(" (no recent events \u2014 waiting for new ones)\n"));
57034
57048
  }
57035
57049
  const interval = setInterval(() => {
57036
- const overlapTs = lastTs ? new Date(new Date(lastTs.replace(" +0000 UTC", "Z")).getTime() - 2e3).toISOString().replace("T", " ").slice(0, 19) : new Date(Date.now() - 1e4).toISOString().replace("T", " ").slice(0, 19);
57037
- const where = buildWhereClause(opts, overlapTs);
57038
- const events = queryEvents(cwd, where, 50);
57039
- const newEvents = events.filter((ev) => !seenIds.has(ev.id));
57040
- for (const ev of newEvents) {
57041
- seenIds.add(ev.id);
57042
- if (!lastTs || ev.created_at > lastTs) lastTs = ev.created_at;
57043
- if (opts.json) {
57044
- console.log(JSON.stringify(ev));
57045
- } else {
57046
- printEvent(ev);
57050
+ const events = queryEvents(dbPath, buildWhere(opts, `id > ${lastId}`), 50);
57051
+ if (events.length > 0) {
57052
+ extendColorMap(colorMap, events);
57053
+ for (const ev of events) {
57054
+ if (ev.id > lastId) lastId = ev.id;
57055
+ opts.json ? console.log(JSON.stringify(ev)) : console.log(" " + formatLine(ev, colorMap));
57047
57056
  }
57048
57057
  }
57049
57058
  }, 2e3);
@@ -57054,44 +57063,36 @@ function watch(cwd, opts) {
57054
57063
  });
57055
57064
  }
57056
57065
  function createDebugCommand() {
57057
- return new Command("debug").description("Watch xtrm hook and bd lifecycle events in real time").option("--all", "Show full history instead of watching", 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) => {
57058
57067
  const cwd = process.cwd();
57059
- const root = findProjectRoot(cwd);
57060
- if (!root) {
57061
- console.error(kleur_default.red("\n \u2717 No beads project found (.beads directory missing)\n"));
57062
- console.error(kleur_default.dim(" Run from inside an xtrm project.\n"));
57063
- 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;
57064
57076
  }
57065
57077
  if (!opts.json) {
57066
57078
  console.log(kleur_default.bold("\n xtrm event log"));
57067
- if (opts.all) {
57068
- console.log(kleur_default.dim(" Showing full history\n"));
57069
- } else {
57070
- console.log(kleur_default.dim(" Watching for events \u2014 Ctrl+C to stop\n"));
57071
- }
57079
+ console.log(kleur_default.dim(opts.all ? " Full history\n" : " Following \u2014 Ctrl+C to stop\n"));
57072
57080
  printHeader();
57073
57081
  }
57074
57082
  if (opts.all) {
57075
- const where = buildWhereClause(opts, null);
57076
- const events = queryEvents(root, where, 200);
57083
+ const events = queryEvents(dbPath, buildWhere(opts, ""), 1e3);
57077
57084
  if (events.length === 0) {
57078
- if (!opts.json) {
57079
- console.log(kleur_default.dim("\n No events recorded yet."));
57080
- console.log(kleur_default.dim(" Events appear here as hooks fire and bd lifecycle runs.\n"));
57081
- }
57085
+ if (!opts.json) console.log(kleur_default.dim("\n No events recorded yet.\n"));
57082
57086
  } else {
57087
+ const colorMap = buildColorMap(events);
57083
57088
  for (const ev of events) {
57084
- if (opts.json) {
57085
- console.log(JSON.stringify(ev));
57086
- } else {
57087
- printEvent(ev);
57088
- }
57089
+ opts.json ? console.log(JSON.stringify(ev)) : console.log(" " + formatLine(ev, colorMap));
57089
57090
  }
57090
57091
  if (!opts.json) console.log("");
57091
57092
  }
57092
57093
  return;
57093
57094
  }
57094
- watch(root, opts);
57095
+ follow(dbPath, opts);
57095
57096
  });
57096
57097
  }
57097
57098