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 +9 -3
- package/cli/dist/index.cjs +186 -185
- 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 +83 -83
- 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
|
|
|
@@ -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
|
-
|
|
56911
|
-
|
|
56912
|
-
|
|
56913
|
-
|
|
56914
|
-
|
|
56915
|
-
|
|
56916
|
-
|
|
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
|
-
|
|
56919
|
-
|
|
56920
|
-
|
|
56921
|
-
|
|
56922
|
-
|
|
56923
|
-
|
|
56924
|
-
|
|
56925
|
-
|
|
56926
|
-
|
|
56927
|
-
|
|
56928
|
-
|
|
56929
|
-
|
|
56930
|
-
|
|
56931
|
-
|
|
56932
|
-
|
|
56933
|
-
|
|
56934
|
-
|
|
56935
|
-
|
|
56936
|
-
|
|
56937
|
-
|
|
56938
|
-
|
|
56939
|
-
|
|
56940
|
-
|
|
56941
|
-
|
|
56942
|
-
|
|
56943
|
-
|
|
56944
|
-
|
|
56945
|
-
if (
|
|
56946
|
-
|
|
56947
|
-
|
|
56948
|
-
|
|
56949
|
-
|
|
56950
|
-
|
|
56951
|
-
|
|
56952
|
-
|
|
56953
|
-
|
|
56954
|
-
|
|
56955
|
-
|
|
56956
|
-
|
|
56957
|
-
|
|
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
|
-
|
|
56961
|
-
|
|
56962
|
-
|
|
56963
|
-
|
|
56964
|
-
|
|
56965
|
-
|
|
56966
|
-
|
|
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
|
|
56970
|
-
const
|
|
56971
|
-
|
|
56972
|
-
|
|
56973
|
-
|
|
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
|
|
56976
|
-
|
|
56977
|
-
return kleur_default.dim(short);
|
|
56957
|
+
function fmtTime(ts) {
|
|
56958
|
+
return new Date(ts).toLocaleTimeString("en-GB", { hour12: false });
|
|
56978
56959
|
}
|
|
56979
|
-
function
|
|
56960
|
+
function buildDetail(event) {
|
|
56980
56961
|
const parts = [];
|
|
56981
|
-
|
|
56982
|
-
if (event.
|
|
56983
|
-
|
|
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
|
|
56987
|
-
const time3 = kleur_default.dim(
|
|
56988
|
-
const
|
|
56989
|
-
const
|
|
56990
|
-
const
|
|
56991
|
-
|
|
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
|
-
` ${
|
|
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(
|
|
56997
|
+
console.log(` ${kleur_default.dim("\u2500".repeat(72))}`);
|
|
57000
56998
|
}
|
|
57001
|
-
function
|
|
57002
|
-
|
|
57003
|
-
|
|
57004
|
-
|
|
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
|
|
57012
|
-
clauses.push(`
|
|
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
|
|
57017
|
-
const
|
|
57018
|
-
|
|
57019
|
-
|
|
57020
|
-
|
|
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 [];
|
|
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
|
-
|
|
57025
|
-
|
|
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
|
-
|
|
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
|
|
57037
|
-
|
|
57038
|
-
|
|
57039
|
-
|
|
57040
|
-
|
|
57041
|
-
|
|
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
|
|
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
|
|
57060
|
-
if (!
|
|
57061
|
-
|
|
57062
|
-
|
|
57063
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
57095
|
+
follow(dbPath, opts);
|
|
57095
57096
|
});
|
|
57096
57097
|
}
|
|
57097
57098
|
|