xtrm-tools 0.6.0 → 0.7.0

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.
@@ -34836,20 +34836,9 @@ function getInstalledPiPackages() {
34836
34836
  if (result.status !== 0) return [];
34837
34837
  const output = result.stdout;
34838
34838
  const packages = [];
34839
- let inUserPackages = false;
34840
34839
  for (const line of output.split("\n")) {
34841
- if (line.includes("User packages:")) {
34842
- inUserPackages = true;
34843
- continue;
34844
- }
34845
- if (line.includes("Project packages:")) {
34846
- inUserPackages = false;
34847
- continue;
34848
- }
34849
- if (inUserPackages) {
34850
- const match = line.match(/^\s+(npm:[\w\-/@]+)/);
34851
- if (match) packages.push(match[1]);
34852
- }
34840
+ const match = line.match(/^\s+(npm:[\w\-/@]+)/);
34841
+ if (match) packages.push(match[1]);
34853
34842
  }
34854
34843
  return packages.sort();
34855
34844
  }
@@ -35394,9 +35383,15 @@ function createPiCommand() {
35394
35383
  console.log("");
35395
35384
  return;
35396
35385
  }
35397
- const repoRoot = await findRepoRoot();
35398
- const sourceDir = import_path6.default.join(repoRoot, "config", "pi", "extensions");
35399
- const projectScopedDir = import_path6.default.join(repoRoot, ".pi", "extensions");
35386
+ const gitResult = (0, import_node_child_process5.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
35387
+ cwd: process.cwd(),
35388
+ encoding: "utf8",
35389
+ stdio: "pipe"
35390
+ });
35391
+ const projectRoot = gitResult.status === 0 ? (gitResult.stdout ?? "").trim() : process.cwd();
35392
+ const bundleRoot = await findRepoRoot();
35393
+ const sourceDir = import_path6.default.join(bundleRoot, "config", "pi", "extensions");
35394
+ const projectScopedDir = import_path6.default.join(projectRoot, ".pi", "extensions");
35400
35395
  const targetDir = await import_fs_extra6.default.pathExists(projectScopedDir) ? projectScopedDir : import_path6.default.join(PI_AGENT_DIR4, "extensions");
35401
35396
  const scopeLabel = targetDir === projectScopedDir ? "project" : "global";
35402
35397
  if (!await import_fs_extra6.default.pathExists(sourceDir)) {
@@ -35458,9 +35453,15 @@ function createPiCommand() {
35458
35453
  console.log(kleur_default.yellow(` \u26A0 missing config: ${missingConfig.join(", ")}`));
35459
35454
  allOk = false;
35460
35455
  }
35461
- const repoRoot = await findRepoRoot();
35462
- const sourceDir = import_path6.default.join(repoRoot, "config", "pi", "extensions");
35463
- const projectScopedDir = import_path6.default.join(repoRoot, ".pi", "extensions");
35456
+ const gitResult = (0, import_node_child_process5.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
35457
+ cwd: process.cwd(),
35458
+ encoding: "utf8",
35459
+ stdio: "pipe"
35460
+ });
35461
+ const projectRoot = gitResult.status === 0 ? (gitResult.stdout ?? "").trim() : process.cwd();
35462
+ const bundleRoot = await findRepoRoot();
35463
+ const sourceDir = import_path6.default.join(bundleRoot, "config", "pi", "extensions");
35464
+ const projectScopedDir = import_path6.default.join(projectRoot, ".pi", "extensions");
35464
35465
  const targetDir = await import_fs_extra6.default.pathExists(projectScopedDir) ? projectScopedDir : import_path6.default.join(PI_AGENT_DIR4, "extensions");
35465
35466
  if (await import_fs_extra6.default.pathExists(sourceDir)) {
35466
35467
  const plan = await inventoryPiRuntime(sourceDir, targetDir);
@@ -35492,8 +35493,13 @@ function createPiCommand() {
35492
35493
  }
35493
35494
  });
35494
35495
  cmd.command("reload").description("Re-sync extensions, remove orphaned, and reinstall missing packages").action(async () => {
35495
- const repoRoot = await findRepoRoot();
35496
- await runPiInstall(false, false, repoRoot);
35496
+ const r = (0, import_node_child_process5.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
35497
+ cwd: process.cwd(),
35498
+ encoding: "utf8",
35499
+ stdio: "pipe"
35500
+ });
35501
+ const projectRoot = r.status === 0 ? (r.stdout ?? "").trim() : process.cwd();
35502
+ await runPiInstall(false, false, projectRoot);
35497
35503
  });
35498
35504
  return cmd;
35499
35505
  }
@@ -35501,12 +35507,13 @@ function createPiCommand() {
35501
35507
  // src/commands/init.ts
35502
35508
  var import_path17 = __toESM(require("path"), 1);
35503
35509
  var import_fs_extra15 = __toESM(require_lib(), 1);
35504
- var import_child_process6 = require("child_process");
35510
+ var import_child_process7 = require("child_process");
35505
35511
  var import_prompts3 = __toESM(require_prompts3(), 1);
35506
35512
 
35507
35513
  // src/commands/install-service-skills.ts
35508
35514
  var import_path7 = __toESM(require("path"), 1);
35509
35515
  var import_fs_extra7 = __toESM(require_lib(), 1);
35516
+ var import_child_process4 = require("child_process");
35510
35517
  function resolvePkgRoot3() {
35511
35518
  const candidates = [
35512
35519
  import_path7.default.resolve(__dirname, "../.."),
@@ -35520,6 +35527,90 @@ function resolvePkgRoot3() {
35520
35527
  }
35521
35528
  var PKG_ROOT = resolvePkgRoot3();
35522
35529
  var SKILLS_SRC = import_path7.default.join(PKG_ROOT, "project-skills", "service-skills-set", ".claude");
35530
+ var MARKER_DOC = "# [jaggers] doc-reminder";
35531
+ var MARKER_STALENESS = "# [jaggers] skill-staleness";
35532
+ var MARKER_CHAIN = "# [jaggers] chain-githooks";
35533
+ async function installGitHooks(projectRoot, skillsSrc = SKILLS_SRC) {
35534
+ const gitHooksSrc = import_path7.default.join(skillsSrc, "git-hooks");
35535
+ const gitHooksDest = import_path7.default.join(projectRoot, ".claude", "git-hooks");
35536
+ await import_fs_extra7.default.copy(gitHooksSrc, gitHooksDest, { overwrite: true });
35537
+ const docScript = import_path7.default.join(projectRoot, ".claude", "git-hooks", "doc_reminder.py");
35538
+ const stalenessScript = import_path7.default.join(projectRoot, ".claude", "git-hooks", "skill_staleness.py");
35539
+ const preCommit = import_path7.default.join(projectRoot, ".githooks", "pre-commit");
35540
+ const prePush = import_path7.default.join(projectRoot, ".githooks", "pre-push");
35541
+ for (const hookPath of [preCommit, prePush]) {
35542
+ if (!await import_fs_extra7.default.pathExists(hookPath)) {
35543
+ await import_fs_extra7.default.mkdirp(import_path7.default.dirname(hookPath));
35544
+ await import_fs_extra7.default.writeFile(hookPath, "#!/usr/bin/env bash\n", { mode: 493 });
35545
+ }
35546
+ }
35547
+ const snippets = [
35548
+ [
35549
+ preCommit,
35550
+ MARKER_DOC,
35551
+ `
35552
+ ${MARKER_DOC}
35553
+ if command -v python3 &>/dev/null && [ -f "${docScript}" ]; then
35554
+ python3 "${docScript}" || true
35555
+ fi
35556
+ `
35557
+ ],
35558
+ [
35559
+ prePush,
35560
+ MARKER_STALENESS,
35561
+ `
35562
+ ${MARKER_STALENESS}
35563
+ if command -v python3 &>/dev/null && [ -f "${stalenessScript}" ]; then
35564
+ python3 "${stalenessScript}" || true
35565
+ fi
35566
+ `
35567
+ ]
35568
+ ];
35569
+ const hookFiles = [];
35570
+ for (const [hookPath, marker, snippet] of snippets) {
35571
+ const content = await import_fs_extra7.default.readFile(hookPath, "utf8");
35572
+ const name = import_path7.default.basename(hookPath);
35573
+ if (!content.includes(marker)) {
35574
+ await import_fs_extra7.default.writeFile(hookPath, content + snippet);
35575
+ hookFiles.push({ name, status: "added" });
35576
+ } else {
35577
+ hookFiles.push({ name, status: "already-present" });
35578
+ }
35579
+ }
35580
+ const hooksPathResult = (0, import_child_process4.spawnSync)("git", ["config", "--get", "core.hooksPath"], {
35581
+ cwd: projectRoot,
35582
+ encoding: "utf8",
35583
+ timeout: 5e3
35584
+ });
35585
+ const configuredHooksPath = hooksPathResult.status === 0 ? hooksPathResult.stdout.trim() : "";
35586
+ const activeHooksDir = configuredHooksPath ? import_path7.default.isAbsolute(configuredHooksPath) ? configuredHooksPath : import_path7.default.join(projectRoot, configuredHooksPath) : import_path7.default.join(projectRoot, ".git", "hooks");
35587
+ const activationTargets = /* @__PURE__ */ new Set([import_path7.default.join(projectRoot, ".git", "hooks"), activeHooksDir]);
35588
+ for (const hooksDir of activationTargets) {
35589
+ await import_fs_extra7.default.mkdirp(hooksDir);
35590
+ for (const [name, sourceHook] of [["pre-commit", preCommit], ["pre-push", prePush]]) {
35591
+ const targetHook = import_path7.default.join(hooksDir, name);
35592
+ if (!await import_fs_extra7.default.pathExists(targetHook)) {
35593
+ await import_fs_extra7.default.writeFile(targetHook, "#!/usr/bin/env bash\n", { mode: 493 });
35594
+ } else {
35595
+ await import_fs_extra7.default.chmod(targetHook, 493);
35596
+ }
35597
+ if (import_path7.default.resolve(targetHook) === import_path7.default.resolve(sourceHook)) {
35598
+ continue;
35599
+ }
35600
+ const chainSnippet = `
35601
+ ${MARKER_CHAIN}
35602
+ if [ -x "${sourceHook}" ]; then
35603
+ "${sourceHook}" "$@"
35604
+ fi
35605
+ `;
35606
+ const targetContent = await import_fs_extra7.default.readFile(targetHook, "utf8");
35607
+ if (!targetContent.includes(MARKER_CHAIN)) {
35608
+ await import_fs_extra7.default.writeFile(targetHook, targetContent + chainSnippet);
35609
+ }
35610
+ }
35611
+ }
35612
+ return { hookFiles };
35613
+ }
35523
35614
 
35524
35615
  // src/commands/install.ts
35525
35616
  var import_prompts2 = __toESM(require_prompts3(), 1);
@@ -37321,6 +37412,7 @@ var Listr = class {
37321
37412
 
37322
37413
  // src/commands/install.ts
37323
37414
  var import_os6 = __toESM(require("os"), 1);
37415
+ var import_node_child_process6 = require("child_process");
37324
37416
 
37325
37417
  // src/core/context.ts
37326
37418
  var import_os3 = __toESM(require("os"), 1);
@@ -39180,7 +39272,7 @@ var ConfigAdapter = class {
39180
39272
  };
39181
39273
 
39182
39274
  // src/utils/sync-mcp-cli.ts
39183
- var import_child_process4 = require("child_process");
39275
+ var import_child_process5 = require("child_process");
39184
39276
  var import_util2 = require("util");
39185
39277
 
39186
39278
  // src/utils/env-manager.ts
@@ -39192,7 +39284,7 @@ var ENV_FILE = import_path13.default.join(CONFIG_DIR, ".env");
39192
39284
  var ENV_EXAMPLE_FILE = import_path13.default.join(CONFIG_DIR, ".env.example");
39193
39285
 
39194
39286
  // src/utils/sync-mcp-cli.ts
39195
- var execAsync = (0, import_util2.promisify)(import_child_process4.exec);
39287
+ var execAsync = (0, import_util2.promisify)(import_child_process5.exec);
39196
39288
 
39197
39289
  // src/core/rollback.ts
39198
39290
  var import_fs_extra12 = __toESM(require_lib(), 1);
@@ -39571,11 +39663,17 @@ async function runInstall(opts = {}) {
39571
39663
  const effectiveYes = yes || process.argv.includes("--yes") || process.argv.includes("-y");
39572
39664
  const syncType = backport ? "backport" : "sync";
39573
39665
  const actionLabel = backport ? "backport" : "install";
39666
+ const gitResult = (0, import_node_child_process6.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
39667
+ cwd: process.cwd(),
39668
+ encoding: "utf8",
39669
+ stdio: "pipe"
39670
+ });
39671
+ const projectRoot = gitResult.status === 0 ? (gitResult.stdout ?? "").trim() : process.cwd();
39574
39672
  const repoRoot = await findRepoRoot();
39575
39673
  const ctx = await getContext({
39576
39674
  createMissingDirs: !dryRun,
39577
39675
  isGlobal,
39578
- projectRoot: repoRoot
39676
+ projectRoot
39579
39677
  });
39580
39678
  const { targets, syncMode } = ctx;
39581
39679
  if (!backport && !skipMachineBootstrap) {
@@ -39583,9 +39681,9 @@ async function runInstall(opts = {}) {
39583
39681
  }
39584
39682
  if (!backport) {
39585
39683
  if (!skipClaudeRuntimeSync) {
39586
- await runClaudeRuntimeSyncPhase({ repoRoot, dryRun, isGlobal });
39684
+ await runClaudeRuntimeSyncPhase({ repoRoot: projectRoot, dryRun, isGlobal });
39587
39685
  }
39588
- await runPiInstall(dryRun, isGlobal, repoRoot);
39686
+ await runPiInstall(dryRun, isGlobal, projectRoot);
39589
39687
  }
39590
39688
  const diffTasks = new Listr(
39591
39689
  targets.map((target) => ({
@@ -39658,7 +39756,7 @@ async function runInstall(opts = {}) {
39658
39756
  }
39659
39757
 
39660
39758
  // src/core/init-verification.ts
39661
- var import_child_process5 = require("child_process");
39759
+ var import_child_process6 = require("child_process");
39662
39760
  var import_fs_extra14 = __toESM(require_lib(), 1);
39663
39761
  var import_path16 = __toESM(require("path"), 1);
39664
39762
  function verifyMachineBootstrap() {
@@ -39671,7 +39769,7 @@ function verifyClaudeRuntime() {
39671
39769
  "github@claude-plugins-official",
39672
39770
  "ralph-loop@claude-plugins-official"
39673
39771
  ];
39674
- const result = (0, import_child_process5.spawnSync)("claude", ["plugin", "list"], { encoding: "utf8", stdio: "pipe" });
39772
+ const result = (0, import_child_process6.spawnSync)("claude", ["plugin", "list"], { encoding: "utf8", stdio: "pipe" });
39675
39773
  const output = result.stdout ?? "";
39676
39774
  const xtrmToolsPlugin = output.includes("xtrm-tools@xtrm-tools") || output.includes("xtrm-tools");
39677
39775
  const officialPlugins = [];
@@ -39705,7 +39803,7 @@ async function verifyPiRuntime(projectRoot) {
39705
39803
  }
39706
39804
  function verifyProjectBootstrap(projectRoot) {
39707
39805
  const beadsInitialized = import_fs_extra14.default.pathExistsSync(import_path16.default.join(projectRoot, ".beads"));
39708
- const gnStatus = (0, import_child_process5.spawnSync)("gitnexus", ["status"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3 });
39806
+ const gnStatus = (0, import_child_process6.spawnSync)("gitnexus", ["status"], { cwd: projectRoot, encoding: "utf8", timeout: 5e3 });
39709
39807
  const gnText = `${gnStatus.stdout ?? ""}
39710
39808
  ${gnStatus.stderr ?? ""}`.toLowerCase();
39711
39809
  const gitnexusIndexed = gnStatus.status === 0 && !gnText.includes("stale") && !gnText.includes("not indexed") && !gnText.includes("missing");
@@ -39918,6 +40016,323 @@ async function injectProjectInstructionHeaders(projectRoot) {
39918
40016
  console.log(`${kleur_default.green(" \u2713")} updated ${target.output}`);
39919
40017
  }
39920
40018
  }
40019
+ async function getAvailableProjectSkills() {
40020
+ if (!await import_fs_extra15.default.pathExists(PROJECT_SKILLS_DIR)) {
40021
+ return [];
40022
+ }
40023
+ const entries = await import_fs_extra15.default.readdir(PROJECT_SKILLS_DIR);
40024
+ const skills = [];
40025
+ for (const entry of entries) {
40026
+ const entryPath = import_path17.default.join(PROJECT_SKILLS_DIR, entry);
40027
+ const stat = await import_fs_extra15.default.stat(entryPath);
40028
+ if (stat.isDirectory() && await import_fs_extra15.default.pathExists(import_path17.default.join(entryPath, ".claude"))) {
40029
+ skills.push(entry);
40030
+ }
40031
+ }
40032
+ return skills.sort();
40033
+ }
40034
+ function getScriptFilename(hook) {
40035
+ const cmd = hook.command || hook.hooks?.[0]?.command || "";
40036
+ if (typeof cmd !== "string") return null;
40037
+ const m = cmd.match(/\/hooks\/([A-Za-z0-9_/-]+\.(?:py|cjs|mjs|js))/);
40038
+ if (m) return m[1];
40039
+ const m2 = cmd.match(/([A-Za-z0-9_-]+\.(?:py|cjs|mjs|js))(?!.*[A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))/);
40040
+ return m2?.[1] ?? null;
40041
+ }
40042
+ function pruneStaleHooks(existing, canonical) {
40043
+ const result = { ...existing };
40044
+ const removed = [];
40045
+ if (!result.hooks || typeof result.hooks !== "object") {
40046
+ return { result, removed };
40047
+ }
40048
+ if (!canonical.hooks || typeof canonical.hooks !== "object") {
40049
+ return { result, removed };
40050
+ }
40051
+ const canonicalScripts = /* @__PURE__ */ new Set();
40052
+ const canonicalBasenames = /* @__PURE__ */ new Set();
40053
+ for (const hooks of Object.values(canonical.hooks)) {
40054
+ const hookList = Array.isArray(hooks) ? hooks : [hooks];
40055
+ for (const wrapper of hookList) {
40056
+ const innerHooks = wrapper.hooks || [wrapper];
40057
+ for (const hook of innerHooks) {
40058
+ const script = getScriptFilename(hook);
40059
+ if (!script) continue;
40060
+ canonicalScripts.add(script);
40061
+ canonicalBasenames.add(import_path17.default.basename(script));
40062
+ }
40063
+ }
40064
+ }
40065
+ for (const [event, hooks] of Object.entries(result.hooks)) {
40066
+ if (!Array.isArray(hooks)) continue;
40067
+ const prunedWrappers = [];
40068
+ for (const wrapper of hooks) {
40069
+ const innerHooks = wrapper.hooks || [wrapper];
40070
+ const keptInner = [];
40071
+ for (const hook of innerHooks) {
40072
+ const script = getScriptFilename(hook);
40073
+ if (!script) {
40074
+ keptInner.push(hook);
40075
+ continue;
40076
+ }
40077
+ if (canonicalScripts.has(script)) {
40078
+ keptInner.push(hook);
40079
+ continue;
40080
+ }
40081
+ const sameSkillFamily = canonicalBasenames.has(import_path17.default.basename(script));
40082
+ if (sameSkillFamily) {
40083
+ removed.push(`${event}:${script}`);
40084
+ continue;
40085
+ }
40086
+ keptInner.push(hook);
40087
+ }
40088
+ if (keptInner.length > 0) {
40089
+ if (wrapper.hooks) {
40090
+ prunedWrappers.push({ ...wrapper, hooks: keptInner });
40091
+ } else if (keptInner.length === 1) {
40092
+ prunedWrappers.push(keptInner[0]);
40093
+ } else {
40094
+ prunedWrappers.push({ ...wrapper, hooks: keptInner });
40095
+ }
40096
+ }
40097
+ }
40098
+ if (prunedWrappers.length > 0) {
40099
+ result.hooks[event] = prunedWrappers;
40100
+ } else {
40101
+ delete result.hooks[event];
40102
+ }
40103
+ }
40104
+ return { result, removed };
40105
+ }
40106
+ function deepMergeHooks(existing, incoming) {
40107
+ const result = { ...existing };
40108
+ if (!result.hooks) result.hooks = {};
40109
+ if (!incoming.hooks) return result;
40110
+ for (const [event, incomingHooks] of Object.entries(incoming.hooks)) {
40111
+ if (!result.hooks[event]) {
40112
+ result.hooks[event] = incomingHooks;
40113
+ } else {
40114
+ const existingEventHooks = Array.isArray(result.hooks[event]) ? result.hooks[event] : [result.hooks[event]];
40115
+ const incomingEventHooks = Array.isArray(incomingHooks) ? incomingHooks : [incomingHooks];
40116
+ const getCommand = (h) => h.command || h.hooks?.[0]?.command;
40117
+ const getCommandKey = (cmd) => {
40118
+ if (!cmd || typeof cmd !== "string") return null;
40119
+ const m = cmd.match(/([A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))(?!.*[A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))/);
40120
+ return m?.[1] ?? null;
40121
+ };
40122
+ const mergeMatcher2 = (existingMatcher, incomingMatcher) => {
40123
+ const existingParts = existingMatcher.split("|").map((s) => s.trim()).filter(Boolean);
40124
+ const incomingParts = incomingMatcher.split("|").map((s) => s.trim()).filter(Boolean);
40125
+ const merged = [...existingParts];
40126
+ for (const part of incomingParts) {
40127
+ if (!merged.includes(part)) merged.push(part);
40128
+ }
40129
+ return merged.join("|");
40130
+ };
40131
+ const mergedEventHooks = [...existingEventHooks];
40132
+ for (const incomingHook of incomingEventHooks) {
40133
+ const incomingCmd = getCommand(incomingHook);
40134
+ if (!incomingCmd) {
40135
+ mergedEventHooks.push(incomingHook);
40136
+ continue;
40137
+ }
40138
+ const incomingKey = getCommandKey(incomingCmd);
40139
+ const existingIndex = mergedEventHooks.findIndex((h) => {
40140
+ const existingCmd = getCommand(h);
40141
+ if (existingCmd === incomingCmd) return true;
40142
+ if (!incomingKey) return false;
40143
+ return getCommandKey(existingCmd) === incomingKey;
40144
+ });
40145
+ if (existingIndex === -1) {
40146
+ mergedEventHooks.push(incomingHook);
40147
+ continue;
40148
+ }
40149
+ const existingHook = mergedEventHooks[existingIndex];
40150
+ if (typeof existingHook.matcher === "string" && typeof incomingHook.matcher === "string") {
40151
+ existingHook.matcher = mergeMatcher2(existingHook.matcher, incomingHook.matcher);
40152
+ }
40153
+ }
40154
+ result.hooks[event] = mergedEventHooks;
40155
+ }
40156
+ }
40157
+ return result;
40158
+ }
40159
+ function extractReadmeDescription(readmeContent) {
40160
+ const lines = readmeContent.split("\n");
40161
+ const headingIndex = lines.findIndex((line) => line.trim().startsWith("# "));
40162
+ const searchStart = headingIndex >= 0 ? headingIndex + 1 : 0;
40163
+ for (const rawLine of lines.slice(searchStart)) {
40164
+ const line = rawLine.trim();
40165
+ if (!line || line.startsWith("#") || line.startsWith("[![") || line.startsWith("<")) {
40166
+ continue;
40167
+ }
40168
+ return line.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[*_`]/g, "").trim();
40169
+ }
40170
+ return "No description available";
40171
+ }
40172
+ async function installProjectSkill(toolName, projectRootOverride) {
40173
+ const skillPath = import_path17.default.join(PROJECT_SKILLS_DIR, toolName);
40174
+ if (!await import_fs_extra15.default.pathExists(skillPath)) {
40175
+ console.error(kleur_default.red(`
40176
+ \u2717 Project skill '${toolName}' not found.
40177
+ `));
40178
+ console.error(kleur_default.dim(` Available project skills:
40179
+ `));
40180
+ await listProjectSkills();
40181
+ process.exit(1);
40182
+ }
40183
+ const projectRoot = projectRootOverride ?? getProjectRoot();
40184
+ const claudeDir = import_path17.default.join(projectRoot, ".claude");
40185
+ console.log(kleur_default.dim(`
40186
+ Installing project skill: ${kleur_default.cyan(toolName)}`));
40187
+ console.log(kleur_default.dim(` Target: ${projectRoot}
40188
+ `));
40189
+ const skillClaudeDir = import_path17.default.join(skillPath, ".claude");
40190
+ const skillSettingsPath = import_path17.default.join(skillClaudeDir, "settings.json");
40191
+ const skillSkillsDir = import_path17.default.join(skillClaudeDir, "skills");
40192
+ const skillReadmePath = import_path17.default.join(skillPath, "README.md");
40193
+ if (await import_fs_extra15.default.pathExists(skillSettingsPath)) {
40194
+ console.log(kleur_default.bold("\u2500\u2500 Installing Hooks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
40195
+ const targetSettingsPath = import_path17.default.join(claudeDir, "settings.json");
40196
+ await import_fs_extra15.default.mkdirp(import_path17.default.dirname(targetSettingsPath));
40197
+ let existingSettings = {};
40198
+ if (await import_fs_extra15.default.pathExists(targetSettingsPath)) {
40199
+ try {
40200
+ existingSettings = JSON.parse(await import_fs_extra15.default.readFile(targetSettingsPath, "utf8"));
40201
+ } catch {
40202
+ }
40203
+ }
40204
+ const incomingSettings = JSON.parse(await import_fs_extra15.default.readFile(skillSettingsPath, "utf8"));
40205
+ const { result: prunedSettings, removed } = pruneStaleHooks(existingSettings, incomingSettings);
40206
+ if (removed.length > 0) {
40207
+ console.log(kleur_default.yellow(` \u21B3 Pruned ${removed.length} stale hook(s): ${removed.join(", ")}`));
40208
+ }
40209
+ const mergedSettings = deepMergeHooks(prunedSettings, incomingSettings);
40210
+ await import_fs_extra15.default.writeFile(targetSettingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
40211
+ console.log(`${kleur_default.green(" \u2713")} settings.json (hooks merged)`);
40212
+ }
40213
+ if (await import_fs_extra15.default.pathExists(skillSkillsDir)) {
40214
+ console.log(kleur_default.bold("\n\u2500\u2500 Installing Skills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
40215
+ const targetSkillsDir = import_path17.default.join(claudeDir, "skills");
40216
+ const skillEntries = await import_fs_extra15.default.readdir(skillSkillsDir);
40217
+ for (const entry of skillEntries) {
40218
+ const src = import_path17.default.join(skillSkillsDir, entry);
40219
+ const dest = import_path17.default.join(targetSkillsDir, entry);
40220
+ await import_fs_extra15.default.copy(src, dest, {
40221
+ filter: (src2) => !src2.includes(".Zone.Identifier") && !src2.includes("__pycache__") && !src2.includes(".pytest_cache") && !src2.endsWith(".pyc")
40222
+ });
40223
+ console.log(`${kleur_default.green(" \u2713")} .claude/skills/${entry}/`);
40224
+ }
40225
+ }
40226
+ if (await import_fs_extra15.default.pathExists(skillClaudeDir)) {
40227
+ const claudeEntries = await import_fs_extra15.default.readdir(skillClaudeDir);
40228
+ for (const entry of claudeEntries) {
40229
+ if (entry === "settings.json" || entry === "skills") {
40230
+ continue;
40231
+ }
40232
+ const src = import_path17.default.join(skillClaudeDir, entry);
40233
+ const dest = import_path17.default.join(claudeDir, entry);
40234
+ await import_fs_extra15.default.copy(src, dest, {
40235
+ filter: (src2) => !src2.includes(".Zone.Identifier") && !src2.includes("__pycache__") && !src2.includes(".pytest_cache") && !src2.endsWith(".pyc")
40236
+ });
40237
+ console.log(`${kleur_default.green(" \u2713")} .claude/${entry}/`);
40238
+ }
40239
+ }
40240
+ const claudeSkillsDir = import_path17.default.join(claudeDir, "skills");
40241
+ if (await import_fs_extra15.default.pathExists(claudeSkillsDir)) {
40242
+ const agentsDir = import_path17.default.join(projectRoot, ".agents");
40243
+ const agentsSkillsLink = import_path17.default.join(agentsDir, "skills");
40244
+ const symlinkTarget = import_path17.default.join("..", ".claude", "skills");
40245
+ let needsSymlink = true;
40246
+ if (await import_fs_extra15.default.pathExists(agentsSkillsLink)) {
40247
+ try {
40248
+ const stat = await import_fs_extra15.default.lstat(agentsSkillsLink);
40249
+ if (stat.isSymbolicLink()) {
40250
+ const current = await import_fs_extra15.default.readlink(agentsSkillsLink);
40251
+ if (current === symlinkTarget) {
40252
+ needsSymlink = false;
40253
+ } else {
40254
+ await import_fs_extra15.default.remove(agentsSkillsLink);
40255
+ }
40256
+ } else {
40257
+ console.log(kleur_default.yellow(" \u26A0 .agents/skills/ is a real directory \u2014 skipping Pi symlink"));
40258
+ needsSymlink = false;
40259
+ }
40260
+ } catch {
40261
+ needsSymlink = true;
40262
+ }
40263
+ }
40264
+ if (needsSymlink) {
40265
+ await import_fs_extra15.default.mkdirp(agentsDir);
40266
+ await import_fs_extra15.default.symlink(symlinkTarget, agentsSkillsLink);
40267
+ console.log(`${kleur_default.green(" \u2713")} .agents/skills \u2192 ../.claude/skills`);
40268
+ } else {
40269
+ console.log(kleur_default.dim(" \u2713 .agents/skills symlink already in place"));
40270
+ }
40271
+ }
40272
+ if (await import_fs_extra15.default.pathExists(skillReadmePath)) {
40273
+ console.log(kleur_default.bold("\n\u2500\u2500 Installing Documentation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
40274
+ const docsDir = import_path17.default.join(claudeDir, "docs");
40275
+ await import_fs_extra15.default.mkdirp(docsDir);
40276
+ const destReadme = import_path17.default.join(docsDir, `${toolName}-readme.md`);
40277
+ await import_fs_extra15.default.copy(skillReadmePath, destReadme);
40278
+ console.log(`${kleur_default.green(" \u2713")} .claude/docs/${toolName}-readme.md`);
40279
+ }
40280
+ if (toolName === "service-skills-set") {
40281
+ console.log(kleur_default.bold("\n\u2500\u2500 Installing Git Hooks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
40282
+ await installGitHooks(projectRoot, skillClaudeDir);
40283
+ console.log(`${kleur_default.green(" \u2713")} .githooks/pre-commit`);
40284
+ console.log(`${kleur_default.green(" \u2713")} .githooks/pre-push`);
40285
+ console.log(`${kleur_default.green(" \u2713")} activated in .git/hooks/`);
40286
+ }
40287
+ console.log(kleur_default.bold("\n\u2500\u2500 Post-Install Steps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
40288
+ console.log(kleur_default.yellow("\n \u26A0 IMPORTANT: Manual setup required!\n"));
40289
+ console.log(kleur_default.white(` ${toolName} requires additional configuration.`));
40290
+ console.log(kleur_default.white(` Please read: ${kleur_default.cyan(".claude/docs/" + toolName + "-readme.md")}
40291
+ `));
40292
+ if (toolName === "tdd-guard") {
40293
+ const tddGuardCheck = (0, import_child_process7.spawnSync)("tdd-guard", ["--version"], { stdio: "pipe" });
40294
+ if (tddGuardCheck.status !== 0) {
40295
+ console.log(kleur_default.red(" \u2717 tdd-guard CLI not found globally!\n"));
40296
+ console.log(kleur_default.white(" Install the global CLI:"));
40297
+ console.log(kleur_default.cyan(" npm install -g tdd-guard\n"));
40298
+ } else {
40299
+ console.log(kleur_default.green(" \u2713 tdd-guard CLI found globally"));
40300
+ }
40301
+ console.log(kleur_default.white("\n Install a test reporter (choose one):"));
40302
+ console.log(kleur_default.dim(" npm install --save-dev tdd-guard-vitest # Vitest"));
40303
+ console.log(kleur_default.dim(" npm install --save-dev tdd-guard-jest # Jest"));
40304
+ console.log(kleur_default.dim(" pip install tdd-guard-pytest # pytest\n"));
40305
+ }
40306
+ if (toolName === "quality-gates") {
40307
+ console.log(kleur_default.white(" Install language dependencies:\n"));
40308
+ console.log(kleur_default.white(" TypeScript:"));
40309
+ console.log(kleur_default.dim(" npm install --save-dev typescript eslint prettier"));
40310
+ console.log(kleur_default.white("\n Python:"));
40311
+ console.log(kleur_default.dim(" pip install ruff mypy"));
40312
+ console.log(kleur_default.white("\n For TDD (test-first) enforcement, install separately:"));
40313
+ console.log(kleur_default.dim(" npm install -g tdd-guard"));
40314
+ console.log(kleur_default.dim(" xtrm install project tdd-guard\n"));
40315
+ }
40316
+ console.log(kleur_default.green(" \u2713 Installation complete!\n"));
40317
+ }
40318
+ async function installAllProjectSkills(projectRootOverride) {
40319
+ const skills = await getAvailableProjectSkills();
40320
+ if (skills.length === 0) {
40321
+ console.log(kleur_default.dim(" No project skills available.\n"));
40322
+ return;
40323
+ }
40324
+ const projectRoot = projectRootOverride ?? getProjectRoot();
40325
+ console.log(kleur_default.bold(`
40326
+ Installing ${skills.length} project skills:
40327
+ `));
40328
+ for (const skill of skills) {
40329
+ console.log(kleur_default.dim(` \u2022 ${skill}`));
40330
+ }
40331
+ console.log("");
40332
+ for (const skill of skills) {
40333
+ await installProjectSkill(skill, projectRoot);
40334
+ }
40335
+ }
39921
40336
  async function runPreflight(projectRoot, opts) {
39922
40337
  const repoRoot = await findRepoRoot().catch(() => projectRoot);
39923
40338
  const bootstrapPlan = inventoryDeps();
@@ -39926,7 +40341,8 @@ async function runPreflight(projectRoot, opts) {
39926
40341
  const ctx = await getContext({
39927
40342
  createMissingDirs: false,
39928
40343
  isGlobal: opts.global,
39929
- projectRoot: repoRoot
40344
+ projectRoot
40345
+ // Target project, not source repo
39930
40346
  });
39931
40347
  for (const target of ctx.targets) {
39932
40348
  try {
@@ -39941,7 +40357,7 @@ async function runPreflight(projectRoot, opts) {
39941
40357
  } catch {
39942
40358
  }
39943
40359
  const needsBdInit = !await import_fs_extra15.default.pathExists(import_path17.default.join(projectRoot, ".beads"));
39944
- const gitnexusStatus = (0, import_child_process6.spawnSync)("gitnexus", ["status"], {
40360
+ const gitnexusStatus = (0, import_child_process7.spawnSync)("gitnexus", ["status"], {
39945
40361
  cwd: projectRoot,
39946
40362
  encoding: "utf8",
39947
40363
  timeout: 5e3
@@ -39974,7 +40390,8 @@ function renderInitPlan(inventory) {
39974
40390
  const projActions = [
39975
40391
  needsBdInit ? "bd init \u2014 initialize beads workspace" : null,
39976
40392
  needsGitNexus ? "gitnexus analyze \u2014 build code index" : null,
39977
- "AGENTS.md + CLAUDE.md \u2014 workflow headers"
40393
+ "AGENTS.md + CLAUDE.md \u2014 workflow headers",
40394
+ "project-skills \u2014 service-skills-set, quality-gates"
39978
40395
  ].filter(Boolean);
39979
40396
  for (const action of projActions) {
39980
40397
  console.log(`${kleur_default.cyan(" \u2022")} ${action}`);
@@ -40000,6 +40417,7 @@ async function runProjectBootstrap(projectRoot) {
40000
40417
  await runBdInitForProject(projectRoot);
40001
40418
  await injectProjectInstructionHeaders(projectRoot);
40002
40419
  await runGitNexusInitForProject(projectRoot);
40420
+ await installAllProjectSkills(projectRoot);
40003
40421
  }
40004
40422
  async function runProjectInit(opts = {}) {
40005
40423
  const { dryRun = false, yes = false } = opts;
@@ -40048,7 +40466,7 @@ async function runProjectInit(opts = {}) {
40048
40466
  }
40049
40467
  async function runBdInitForProject(projectRoot) {
40050
40468
  console.log(kleur_default.bold("Running beads initialization (bd init)..."));
40051
- const result = (0, import_child_process6.spawnSync)("bd", ["init"], {
40469
+ const result = (0, import_child_process7.spawnSync)("bd", ["init"], {
40052
40470
  cwd: projectRoot,
40053
40471
  encoding: "utf8",
40054
40472
  timeout: 15e3
@@ -40073,7 +40491,7 @@ ${result.stderr || ""}`.toLowerCase();
40073
40491
  if (result.stderr) process.stderr.write(result.stderr);
40074
40492
  }
40075
40493
  async function runGitNexusInitForProject(projectRoot) {
40076
- const gitnexusCheck = (0, import_child_process6.spawnSync)("gitnexus", ["--version"], {
40494
+ const gitnexusCheck = (0, import_child_process7.spawnSync)("gitnexus", ["--version"], {
40077
40495
  cwd: projectRoot,
40078
40496
  encoding: "utf8",
40079
40497
  timeout: 5e3
@@ -40084,7 +40502,7 @@ async function runGitNexusInitForProject(projectRoot) {
40084
40502
  return;
40085
40503
  }
40086
40504
  console.log(kleur_default.bold("Checking GitNexus index status..."));
40087
- const status = (0, import_child_process6.spawnSync)("gitnexus", ["status"], {
40505
+ const status = (0, import_child_process7.spawnSync)("gitnexus", ["status"], {
40088
40506
  cwd: projectRoot,
40089
40507
  encoding: "utf8",
40090
40508
  timeout: 1e4
@@ -40097,7 +40515,7 @@ ${status.stderr || ""}`.toLowerCase();
40097
40515
  return;
40098
40516
  }
40099
40517
  console.log(kleur_default.bold("Running GitNexus indexing (gitnexus analyze)..."));
40100
- const analyze = (0, import_child_process6.spawnSync)("gitnexus", ["analyze"], {
40518
+ const analyze = (0, import_child_process7.spawnSync)("gitnexus", ["analyze"], {
40101
40519
  cwd: projectRoot,
40102
40520
  encoding: "utf8",
40103
40521
  timeout: 12e4
@@ -40110,8 +40528,46 @@ ${status.stderr || ""}`.toLowerCase();
40110
40528
  if (analyze.stderr) process.stderr.write(analyze.stderr);
40111
40529
  console.log(kleur_default.yellow(` \u26A0 gitnexus analyze exited with code ${analyze.status}`));
40112
40530
  }
40531
+ async function listProjectSkills() {
40532
+ const entries = await getAvailableProjectSkills();
40533
+ if (entries.length === 0) {
40534
+ console.log(kleur_default.dim(" No project skills available.\n"));
40535
+ return;
40536
+ }
40537
+ const skills = [];
40538
+ for (const entry of entries) {
40539
+ const readmePath = import_path17.default.join(PROJECT_SKILLS_DIR, entry, "README.md");
40540
+ let description = "No description available";
40541
+ if (await import_fs_extra15.default.pathExists(readmePath)) {
40542
+ const readmeContent = await import_fs_extra15.default.readFile(readmePath, "utf8");
40543
+ description = extractReadmeDescription(readmeContent).slice(0, 80);
40544
+ }
40545
+ skills.push({ name: entry, description });
40546
+ }
40547
+ if (skills.length === 0) {
40548
+ console.log(kleur_default.dim(" No project skills available.\n"));
40549
+ return;
40550
+ }
40551
+ console.log(kleur_default.bold("\nAvailable Project Skills:\n"));
40552
+ const Table = require_cli_table3();
40553
+ const table = new Table({
40554
+ head: [kleur_default.cyan("Skill"), kleur_default.cyan("Description")],
40555
+ colWidths: [25, 60],
40556
+ style: { head: [], border: [] }
40557
+ });
40558
+ for (const skill of skills) {
40559
+ table.push([kleur_default.white(skill.name), kleur_default.dim(skill.description)]);
40560
+ }
40561
+ console.log(table.toString());
40562
+ console.log(kleur_default.bold("\n\nUsage (legacy):\n"));
40563
+ console.log(kleur_default.dim(" xtrm install project <skill-name> Install a legacy project skill"));
40564
+ console.log(kleur_default.dim(" xtrm install project all Install all legacy project skills"));
40565
+ console.log(kleur_default.dim(" xtrm install project list List available legacy skills\n"));
40566
+ console.log(kleur_default.bold("Preferred:\n"));
40567
+ console.log(kleur_default.dim(" xtrm init Bootstrap project data for global hooks/skills\n"));
40568
+ }
40113
40569
  function getProjectRoot() {
40114
- const result = (0, import_child_process6.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
40570
+ const result = (0, import_child_process7.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
40115
40571
  encoding: "utf8",
40116
40572
  timeout: 5e3
40117
40573
  });
@@ -54488,17 +54944,17 @@ function createCleanCommand() {
54488
54944
 
54489
54945
  // src/commands/end.ts
54490
54946
  var import_prompts5 = __toESM(require_prompts3(), 1);
54491
- var import_node_child_process6 = require("child_process");
54947
+ var import_node_child_process7 = require("child_process");
54492
54948
  function git(args, cwd) {
54493
- const r = (0, import_node_child_process6.spawnSync)("git", args, { cwd, encoding: "utf8", stdio: "pipe" });
54949
+ const r = (0, import_node_child_process7.spawnSync)("git", args, { cwd, encoding: "utf8", stdio: "pipe" });
54494
54950
  return { ok: r.status === 0, out: (r.stdout ?? "").trim(), err: (r.stderr ?? "").trim() };
54495
54951
  }
54496
54952
  function bd(args, cwd) {
54497
- const r = (0, import_node_child_process6.spawnSync)("bd", args, { cwd, encoding: "utf8", stdio: "pipe" });
54953
+ const r = (0, import_node_child_process7.spawnSync)("bd", args, { cwd, encoding: "utf8", stdio: "pipe" });
54498
54954
  return { ok: r.status === 0, out: (r.stdout ?? "").trim() };
54499
54955
  }
54500
54956
  function npm(args, cwd) {
54501
- const r = (0, import_node_child_process6.spawnSync)("npm", args, { cwd, encoding: "utf8", stdio: "pipe" });
54957
+ const r = (0, import_node_child_process7.spawnSync)("npm", args, { cwd, encoding: "utf8", stdio: "pipe" });
54502
54958
  return { ok: r.status === 0, out: (r.stdout ?? "").trim(), err: (r.stderr ?? "").trim() };
54503
54959
  }
54504
54960
  var CONVENTIONAL_SCOPES = /* @__PURE__ */ new Set([
@@ -54757,7 +55213,7 @@ function createEndCommand() {
54757
55213
  console.log(kleur_default.dim(" Creating PR..."));
54758
55214
  const prArgs = ["pr", "create", "--title", prTitle, "--body", prBody];
54759
55215
  if (opts.draft) prArgs.push("--draft");
54760
- const prResult = (0, import_node_child_process6.spawnSync)("gh", prArgs, { cwd, encoding: "utf8", stdio: "pipe" });
55216
+ const prResult = (0, import_node_child_process7.spawnSync)("gh", prArgs, { cwd, encoding: "utf8", stdio: "pipe" });
54761
55217
  if (prResult.status !== 0) {
54762
55218
  console.error(kleur_default.red(`
54763
55219
  \u2717 PR creation failed:
@@ -54788,7 +55244,7 @@ function createEndCommand() {
54788
55244
  try {
54789
55245
  const repoRoot = git(["rev-parse", "--show-toplevel"], cwd).out;
54790
55246
  unregisterPluginsForWorktree(cwd);
54791
- const removeResult = (0, import_node_child_process6.spawnSync)(
55247
+ const removeResult = (0, import_node_child_process7.spawnSync)(
54792
55248
  "git",
54793
55249
  ["worktree", "remove", cwd, "--force"],
54794
55250
  { cwd: repoRoot, encoding: "utf8", stdio: "pipe" }
@@ -54812,11 +55268,11 @@ function createEndCommand() {
54812
55268
 
54813
55269
  // src/commands/worktree.ts
54814
55270
  var import_prompts6 = __toESM(require_prompts3(), 1);
54815
- var import_node_child_process7 = require("child_process");
55271
+ var import_node_child_process8 = require("child_process");
54816
55272
  var import_node_fs5 = require("fs");
54817
55273
  var import_node_path6 = require("path");
54818
55274
  function listXtWorktrees(repoRoot) {
54819
- const r = (0, import_node_child_process7.spawnSync)("git", ["worktree", "list", "--porcelain"], {
55275
+ const r = (0, import_node_child_process8.spawnSync)("git", ["worktree", "list", "--porcelain"], {
54820
55276
  cwd: repoRoot,
54821
55277
  encoding: "utf8",
54822
55278
  stdio: "pipe"
@@ -54850,7 +55306,7 @@ function listXtWorktrees(repoRoot) {
54850
55306
  wt.launchedAt = meta3.launchedAt;
54851
55307
  } catch {
54852
55308
  }
54853
- const logR = (0, import_node_child_process7.spawnSync)("git", ["log", "-1", "--format=%ci%s", "HEAD"], {
55309
+ const logR = (0, import_node_child_process8.spawnSync)("git", ["log", "-1", "--format=%ci%s", "HEAD"], {
54854
55310
  cwd: wt.path,
54855
55311
  encoding: "utf8",
54856
55312
  stdio: "pipe"
@@ -54867,7 +55323,7 @@ function listXtWorktrees(repoRoot) {
54867
55323
  }
54868
55324
  function isMergedIntoMain(branch, repoRoot) {
54869
55325
  const branchShort = branch.replace("refs/heads/", "");
54870
- const r = (0, import_node_child_process7.spawnSync)("git", ["branch", "--merged", "origin/main", "--list", branchShort], {
55326
+ const r = (0, import_node_child_process8.spawnSync)("git", ["branch", "--merged", "origin/main", "--list", branchShort], {
54871
55327
  cwd: repoRoot,
54872
55328
  encoding: "utf8",
54873
55329
  stdio: "pipe"
@@ -54876,7 +55332,7 @@ function isMergedIntoMain(branch, repoRoot) {
54876
55332
  }
54877
55333
  function getPrStatus(branch, repoRoot) {
54878
55334
  const branchShort = branch.replace("refs/heads/", "");
54879
- const r = (0, import_node_child_process7.spawnSync)("gh", ["pr", "list", "--head", branchShort, "--state", "all", "--json", "state,url", "--limit", "1"], {
55335
+ const r = (0, import_node_child_process8.spawnSync)("gh", ["pr", "list", "--head", branchShort, "--state", "all", "--json", "state,url", "--limit", "1"], {
54880
55336
  cwd: repoRoot,
54881
55337
  encoding: "utf8",
54882
55338
  stdio: "pipe"
@@ -54891,7 +55347,7 @@ function getPrStatus(branch, repoRoot) {
54891
55347
  }
54892
55348
  }
54893
55349
  function getRepoRoot(cwd) {
54894
- const r = (0, import_node_child_process7.spawnSync)("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8", stdio: "pipe" });
55350
+ const r = (0, import_node_child_process8.spawnSync)("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8", stdio: "pipe" });
54895
55351
  return r.ok ? r.stdout.trim() : cwd;
54896
55352
  }
54897
55353
  function createWorktreeCommand() {
@@ -54953,7 +55409,7 @@ function createWorktreeCommand() {
54953
55409
  return;
54954
55410
  }
54955
55411
  for (const wt of merged) {
54956
- const r = (0, import_node_child_process7.spawnSync)("git", ["worktree", "remove", wt.path, "--force"], {
55412
+ const r = (0, import_node_child_process8.spawnSync)("git", ["worktree", "remove", wt.path, "--force"], {
54957
55413
  cwd: repoRoot,
54958
55414
  encoding: "utf8",
54959
55415
  stdio: "pipe"
@@ -54994,7 +55450,7 @@ function createWorktreeCommand() {
54994
55450
  console.log(kleur_default.dim(" Cancelled\n"));
54995
55451
  return;
54996
55452
  }
54997
- const r = (0, import_node_child_process7.spawnSync)("git", ["worktree", "remove", target.path, "--force"], {
55453
+ const r = (0, import_node_child_process8.spawnSync)("git", ["worktree", "remove", target.path, "--force"], {
54998
55454
  cwd: repoRoot,
54999
55455
  encoding: "utf8",
55000
55456
  stdio: "pipe"
@@ -55023,7 +55479,7 @@ function clearStatuslineClaim(repoRoot) {
55023
55479
 
55024
55480
  // src/commands/attach.ts
55025
55481
  var import_prompts7 = __toESM(require_prompts3(), 1);
55026
- var import_node_child_process8 = require("child_process");
55482
+ var import_node_child_process9 = require("child_process");
55027
55483
  function createAttachCommand() {
55028
55484
  return new Command("attach").description("Re-attach to an existing xt worktree and resume the Claude or Pi session").argument("[name]", 'Worktree slug or branch name to attach to (e.g. "abc1" or "xt/abc1")').action(async (name) => {
55029
55485
  const repoRoot = getRepoRoot(process.cwd());
@@ -55078,7 +55534,7 @@ function createAttachCommand() {
55078
55534
  console.log(kleur_default.dim(` runtime: ${runtime} (resuming session)`));
55079
55535
  console.log(kleur_default.dim(` path: ${target.path}
55080
55536
  `));
55081
- const result = (0, import_node_child_process8.spawnSync)(runtime, resumeArgs, {
55537
+ const result = (0, import_node_child_process9.spawnSync)(runtime, resumeArgs, {
55082
55538
  cwd: target.path,
55083
55539
  stdio: "inherit"
55084
55540
  });
@@ -55241,16 +55697,16 @@ function isCacheValid(cache, entries, ttlMs = DEFAULT_TTL_MS) {
55241
55697
  }
55242
55698
 
55243
55699
  // src/commands/docs-cross-check-gh.ts
55244
- var import_node_child_process9 = require("child_process");
55700
+ var import_node_child_process10 = require("child_process");
55245
55701
  function isGhAvailable() {
55246
- return (0, import_node_child_process9.spawnSync)("gh", ["--version"], { stdio: "pipe", encoding: "utf8" }).status === 0;
55702
+ return (0, import_node_child_process10.spawnSync)("gh", ["--version"], { stdio: "pipe", encoding: "utf8" }).status === 0;
55247
55703
  }
55248
55704
  function fetchRecentPrs(repoRoot, days) {
55249
55705
  if (!isGhAvailable()) {
55250
55706
  console.log(kleur_default.yellow(" \u26A0 gh CLI not found \u2014 skipping PR data (install gh to enable cross-check)"));
55251
55707
  return [];
55252
55708
  }
55253
- const r = (0, import_node_child_process9.spawnSync)("gh", [
55709
+ const r = (0, import_node_child_process10.spawnSync)("gh", [
55254
55710
  "pr",
55255
55711
  "list",
55256
55712
  "--state",
@@ -55280,11 +55736,11 @@ function fetchRecentPrs(repoRoot, days) {
55280
55736
  }
55281
55737
 
55282
55738
  // src/commands/docs-cross-check-bd.ts
55283
- var import_node_child_process10 = require("child_process");
55739
+ var import_node_child_process11 = require("child_process");
55284
55740
  var _bdAvailable = null;
55285
55741
  function isBdAvailable() {
55286
55742
  if (_bdAvailable !== null) return _bdAvailable;
55287
- const r = (0, import_node_child_process10.spawnSync)("bd", ["--version"], { stdio: "pipe", encoding: "utf8" });
55743
+ const r = (0, import_node_child_process11.spawnSync)("bd", ["--version"], { stdio: "pipe", encoding: "utf8" });
55288
55744
  _bdAvailable = r.status === 0;
55289
55745
  return _bdAvailable;
55290
55746
  }
@@ -55296,7 +55752,7 @@ function fetchClosedBdIssues(days) {
55296
55752
  logBdWarning("fetchClosedBdIssues: bd CLI not available, skipping issue fetch");
55297
55753
  return [];
55298
55754
  }
55299
- const r = (0, import_node_child_process10.spawnSync)("bd", [
55755
+ const r = (0, import_node_child_process11.spawnSync)("bd", [
55300
55756
  "query",
55301
55757
  `status=closed AND updated>${days}d`,
55302
55758
  "--json"
@@ -55823,7 +56279,7 @@ ${content}`;
55823
56279
  }
55824
56280
 
55825
56281
  // src/commands/memory.ts
55826
- var import_node_child_process11 = require("child_process");
56282
+ var import_node_child_process12 = require("child_process");
55827
56283
  var import_node_fs6 = require("fs");
55828
56284
  var import_node_path7 = require("path");
55829
56285
  function createMemoryCommand() {
@@ -55832,14 +56288,14 @@ function createMemoryCommand() {
55832
56288
  function createMemoryUpdateCommand() {
55833
56289
  return new Command("update").description("Run memory-processor specialist to synthesize bd memories into .xtrm/memory.md").option("--dry-run", "Report only \u2014 do not modify memories or write memory.md", false).option("--no-beads", "Skip creating a tracking bead for this run", false).action(async (opts) => {
55834
56290
  const cwd = process.cwd();
55835
- const check2 = (0, import_node_child_process11.spawnSync)("specialists", ["--version"], { encoding: "utf8", stdio: "pipe" });
56291
+ const check2 = (0, import_node_child_process12.spawnSync)("specialists", ["--version"], { encoding: "utf8", stdio: "pipe" });
55836
56292
  if (check2.status !== 0) {
55837
56293
  console.error(kleur_default.red(
55838
56294
  "\n \u2717 specialists CLI not found.\n Install with: npm install -g @jaggerxtrm/specialists\n"
55839
56295
  ));
55840
56296
  process.exit(1);
55841
56297
  }
55842
- const list = (0, import_node_child_process11.spawnSync)("specialists", ["list", "--json"], { cwd, encoding: "utf8", stdio: "pipe" });
56298
+ const list = (0, import_node_child_process12.spawnSync)("specialists", ["list", "--json"], { cwd, encoding: "utf8", stdio: "pipe" });
55843
56299
  if (list.status === 0) {
55844
56300
  try {
55845
56301
  const specialists = JSON.parse(list.stdout);
@@ -55863,7 +56319,7 @@ function createMemoryUpdateCommand() {
55863
56319
  console.log(kleur_default.dim(` ${spinnerText}
55864
56320
  `));
55865
56321
  const exitCode = await new Promise((resolve2) => {
55866
- const proc = (0, import_node_child_process11.spawn)("specialists", args, { cwd, stdio: "inherit" });
56322
+ const proc = (0, import_node_child_process12.spawn)("specialists", args, { cwd, stdio: "inherit" });
55867
56323
  proc.on("close", (code) => resolve2(code ?? 0));
55868
56324
  });
55869
56325
  if (exitCode !== 0) {
@@ -55874,39 +56330,39 @@ function createMemoryUpdateCommand() {
55874
56330
  }
55875
56331
 
55876
56332
  // src/commands/merge.ts
55877
- var import_node_child_process12 = require("child_process");
56333
+ var import_node_child_process13 = require("child_process");
55878
56334
  var import_node_fs7 = require("fs");
55879
56335
  var import_node_path8 = require("path");
55880
56336
  function createMergeCommand() {
55881
56337
  return new Command("merge").description("Drain the xt worktree PR merge queue via the xt-merge specialist").option("--dry-run", "List queue and CI status without merging", false).option("--no-beads", "Skip creating a tracking bead for this run", false).action(async (opts) => {
55882
56338
  const cwd = process.cwd();
55883
- const gitCheck = (0, import_node_child_process12.spawnSync)("git", ["rev-parse", "--git-dir"], { cwd, encoding: "utf8", stdio: "pipe" });
56339
+ const gitCheck = (0, import_node_child_process13.spawnSync)("git", ["rev-parse", "--git-dir"], { cwd, encoding: "utf8", stdio: "pipe" });
55884
56340
  if (gitCheck.status !== 0) {
55885
56341
  console.error(kleur_default.red("\n \u2717 Not inside a git repository.\n"));
55886
56342
  process.exit(1);
55887
56343
  }
55888
- const ghAuth = (0, import_node_child_process12.spawnSync)("gh", ["auth", "status"], { cwd, encoding: "utf8", stdio: "pipe" });
56344
+ const ghAuth = (0, import_node_child_process13.spawnSync)("gh", ["auth", "status"], { cwd, encoding: "utf8", stdio: "pipe" });
55889
56345
  if (ghAuth.status !== 0) {
55890
56346
  console.error(kleur_default.red(
55891
56347
  "\n \u2717 gh is not authenticated.\n Run: gh auth login\n"
55892
56348
  ));
55893
56349
  process.exit(1);
55894
56350
  }
55895
- const dirty = (0, import_node_child_process12.spawnSync)("git", ["status", "--porcelain", "--", ":!.beads/", ":!.specialists/"], { cwd, encoding: "utf8", stdio: "pipe" });
56351
+ const dirty = (0, import_node_child_process13.spawnSync)("git", ["status", "--porcelain", "--", ":!.beads/", ":!.specialists/"], { cwd, encoding: "utf8", stdio: "pipe" });
55896
56352
  if (dirty.stdout.trim().length > 0) {
55897
56353
  console.error(kleur_default.yellow(
55898
56354
  '\n \u26A0 Uncommitted changes detected.\n The rebase cascade will check out other branches \u2014 a dirty tree\n will either fail or carry changes onto the wrong branch.\n\n Stash first: git stash push -m "xt-merge cascade stash"\n Then re-run: xt merge\n'
55899
56355
  ));
55900
56356
  process.exit(1);
55901
56357
  }
55902
- const check2 = (0, import_node_child_process12.spawnSync)("specialists", ["--version"], { encoding: "utf8", stdio: "pipe" });
56358
+ const check2 = (0, import_node_child_process13.spawnSync)("specialists", ["--version"], { encoding: "utf8", stdio: "pipe" });
55903
56359
  if (check2.status !== 0) {
55904
56360
  console.error(kleur_default.red(
55905
56361
  "\n \u2717 specialists CLI not found.\n Install with: npm install -g @jaggerxtrm/specialists\n"
55906
56362
  ));
55907
56363
  process.exit(1);
55908
56364
  }
55909
- const list = (0, import_node_child_process12.spawnSync)("specialists", ["list", "--json"], { cwd, encoding: "utf8", stdio: "pipe" });
56365
+ const list = (0, import_node_child_process13.spawnSync)("specialists", ["list", "--json"], { cwd, encoding: "utf8", stdio: "pipe" });
55910
56366
  if (list.status === 0) {
55911
56367
  try {
55912
56368
  const specialists = JSON.parse(list.stdout);
@@ -55934,7 +56390,7 @@ function createMergeCommand() {
55934
56390
  } catch {
55935
56391
  jobsBefore = /* @__PURE__ */ new Set();
55936
56392
  }
55937
- const runProc = (0, import_node_child_process12.spawn)("specialists", args, { cwd, detached: true, stdio: "ignore" });
56393
+ const runProc = (0, import_node_child_process13.spawn)("specialists", args, { cwd, detached: true, stdio: "ignore" });
55938
56394
  runProc.unref();
55939
56395
  const jobId = await (async () => {
55940
56396
  const deadline = Date.now() + 15e3;
@@ -55953,13 +56409,13 @@ function createMergeCommand() {
55953
56409
  console.error(kleur_default.red("\n \u2717 Timed out waiting for xt-merge job to start.\n"));
55954
56410
  process.exit(1);
55955
56411
  }
55956
- (0, import_node_child_process12.spawnSync)("specialists", ["poll", jobId, "--follow"], { cwd, stdio: "inherit" });
56412
+ (0, import_node_child_process13.spawnSync)("specialists", ["poll", jobId, "--follow"], { cwd, stdio: "inherit" });
55957
56413
  process.exit(0);
55958
56414
  });
55959
56415
  }
55960
56416
 
55961
56417
  // src/commands/debug.ts
55962
- var import_node_child_process13 = require("child_process");
56418
+ var import_node_child_process14 = require("child_process");
55963
56419
  var import_node_fs8 = require("fs");
55964
56420
  var import_node_path9 = require("path");
55965
56421
  var KIND_LABELS = {
@@ -56107,7 +56563,7 @@ function buildWhere(opts, base) {
56107
56563
  }
56108
56564
  function queryEvents(dbPath, where, limit) {
56109
56565
  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}`;
56110
- const result = (0, import_node_child_process13.spawnSync)("sqlite3", [dbPath, "-json", sql], {
56566
+ const result = (0, import_node_child_process14.spawnSync)("sqlite3", [dbPath, "-json", sql], {
56111
56567
  stdio: ["pipe", "pipe", "pipe"],
56112
56568
  encoding: "utf8",
56113
56569
  timeout: 5e3