xtrm-cli 0.5.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.
Files changed (93) hide show
  1. package/.gemini/settings.json +39 -0
  2. package/dist/index.cjs +57378 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2 -0
  5. package/extensions/beads.ts +109 -0
  6. package/extensions/core/adapter.ts +45 -0
  7. package/extensions/core/lib.ts +3 -0
  8. package/extensions/core/logger.ts +45 -0
  9. package/extensions/core/runner.ts +71 -0
  10. package/extensions/custom-footer.ts +160 -0
  11. package/extensions/main-guard-post-push.ts +44 -0
  12. package/extensions/main-guard.ts +126 -0
  13. package/extensions/minimal-mode.ts +201 -0
  14. package/extensions/quality-gates.ts +67 -0
  15. package/extensions/service-skills.ts +150 -0
  16. package/extensions/xtrm-loader.ts +89 -0
  17. package/hooks/gitnexus-impact-reminder.py +13 -0
  18. package/lib/atomic-config.js +236 -0
  19. package/lib/config-adapter.js +231 -0
  20. package/lib/config-injector.js +80 -0
  21. package/lib/context.js +73 -0
  22. package/lib/diff.js +142 -0
  23. package/lib/env-manager.js +160 -0
  24. package/lib/sync-mcp-cli.js +345 -0
  25. package/lib/sync.js +227 -0
  26. package/package.json +47 -0
  27. package/src/adapters/base.ts +29 -0
  28. package/src/adapters/claude.ts +38 -0
  29. package/src/adapters/registry.ts +21 -0
  30. package/src/commands/claude.ts +122 -0
  31. package/src/commands/clean.ts +371 -0
  32. package/src/commands/end.ts +239 -0
  33. package/src/commands/finish.ts +25 -0
  34. package/src/commands/help.ts +180 -0
  35. package/src/commands/init.ts +959 -0
  36. package/src/commands/install-pi.ts +276 -0
  37. package/src/commands/install-service-skills.ts +281 -0
  38. package/src/commands/install.ts +427 -0
  39. package/src/commands/pi-install.ts +119 -0
  40. package/src/commands/pi.ts +128 -0
  41. package/src/commands/reset.ts +12 -0
  42. package/src/commands/status.ts +170 -0
  43. package/src/commands/worktree.ts +193 -0
  44. package/src/core/context.ts +141 -0
  45. package/src/core/diff.ts +174 -0
  46. package/src/core/interactive-plan.ts +165 -0
  47. package/src/core/manifest.ts +26 -0
  48. package/src/core/preflight.ts +142 -0
  49. package/src/core/rollback.ts +32 -0
  50. package/src/core/session-state.ts +139 -0
  51. package/src/core/sync-executor.ts +427 -0
  52. package/src/core/xtrm-finish.ts +267 -0
  53. package/src/index.ts +87 -0
  54. package/src/tests/policy-parity.test.ts +204 -0
  55. package/src/tests/session-flow-parity.test.ts +118 -0
  56. package/src/tests/session-state.test.ts +124 -0
  57. package/src/tests/xtrm-finish.test.ts +148 -0
  58. package/src/types/config.ts +51 -0
  59. package/src/types/models.ts +52 -0
  60. package/src/utils/atomic-config.ts +467 -0
  61. package/src/utils/banner.ts +194 -0
  62. package/src/utils/config-adapter.ts +90 -0
  63. package/src/utils/config-injector.ts +81 -0
  64. package/src/utils/env-manager.ts +193 -0
  65. package/src/utils/hash.ts +42 -0
  66. package/src/utils/repo-root.ts +39 -0
  67. package/src/utils/sync-mcp-cli.ts +395 -0
  68. package/src/utils/theme.ts +37 -0
  69. package/src/utils/worktree-session.ts +93 -0
  70. package/test/atomic-config-prune.test.ts +101 -0
  71. package/test/atomic-config.test.ts +138 -0
  72. package/test/clean.test.ts +172 -0
  73. package/test/config-schema.test.ts +52 -0
  74. package/test/context.test.ts +33 -0
  75. package/test/end-worktree.test.ts +168 -0
  76. package/test/extensions/beads.test.ts +166 -0
  77. package/test/extensions/extension-harness.ts +85 -0
  78. package/test/extensions/main-guard.test.ts +77 -0
  79. package/test/extensions/minimal-mode.test.ts +107 -0
  80. package/test/extensions/quality-gates.test.ts +79 -0
  81. package/test/extensions/service-skills.test.ts +84 -0
  82. package/test/extensions/xtrm-loader.test.ts +53 -0
  83. package/test/hooks/quality-check-hooks.test.ts +45 -0
  84. package/test/hooks.test.ts +1075 -0
  85. package/test/install-pi.test.ts +185 -0
  86. package/test/install-project.test.ts +378 -0
  87. package/test/install-service-skills.test.ts +131 -0
  88. package/test/install-surface.test.ts +72 -0
  89. package/test/runtime-subcommands.test.ts +121 -0
  90. package/test/session-launcher.test.ts +139 -0
  91. package/tsconfig.json +22 -0
  92. package/tsup.config.ts +17 -0
  93. package/vitest.config.ts +10 -0
@@ -0,0 +1,180 @@
1
+ import { Command } from 'commander';
2
+ import kleur from 'kleur';
3
+
4
+ import path from 'path';
5
+ import fs from 'fs-extra';
6
+ import { findRepoRoot } from '../utils/repo-root.js';
7
+ declare const __dirname: string;
8
+
9
+ const HOOK_CATALOG: Array<{ file: string; event: string; desc: string; beads?: true; sessionFlow?: true }> = [
10
+ { file: 'main-guard.mjs', event: 'PreToolUse', desc: 'Blocks direct edits / unsafe Bash on protected branches' },
11
+ { file: 'main-guard-post-push.mjs', event: 'PostToolUse', desc: 'After feature-branch push, reminds PR/merge/sync steps' },
12
+ { file: 'serena-workflow-reminder.py', event: 'SessionStart', desc: 'Injects Serena semantic editing workflow reminder' },
13
+ { file: 'gitnexus/gitnexus-hook.cjs', event: 'PostToolUse', desc: 'Adds GitNexus context for search and Serena tooling' },
14
+ { file: 'agent_context.py', event: 'Support module', desc: 'Shared hook I/O helper used by Python hook scripts' },
15
+ { file: 'beads-edit-gate.mjs', event: 'PreToolUse', desc: 'Blocks file edits if no beads issue is claimed', beads: true },
16
+ { file: 'beads-commit-gate.mjs', event: 'PreToolUse', desc: 'Blocks commits when no beads issue is in progress', beads: true },
17
+ { file: 'beads-memory-gate.mjs', event: 'Stop', desc: 'Prompts memory save when claim was closed', beads: true },
18
+ { file: 'beads-compact-save.mjs', event: 'PreCompact', desc: 'Saves in_progress issue + session-state bundle', beads: true },
19
+ { file: 'beads-compact-restore.mjs', event: 'SessionStart', desc: 'Restores compacted issue + session-state bundle', beads: true },
20
+ { file: 'beads-claim-sync.mjs', event: 'PostToolUse', desc: 'Auto-claim sync + worktree/session-state bootstrap', sessionFlow: true },
21
+ { file: 'beads-stop-gate.mjs', event: 'Stop', desc: 'Blocks stop for waiting-merge/conflicting/pending-cleanup', sessionFlow: true },
22
+ { file: 'branch-state.mjs', event: 'UserPromptSubmit', desc: 'Injects current git branch into prompt context' },
23
+ { file: 'quality-check.cjs', event: 'PostToolUse', desc: 'Runs JS/TS quality checks on mutating edits' },
24
+ { file: 'quality-check.py', event: 'PostToolUse', desc: 'Runs Python quality checks on mutating edits' },
25
+ ];
26
+
27
+ async function readSkillsFromDir(dir: string): Promise<Array<{ name: string; desc: string }>> {
28
+ if (!(await fs.pathExists(dir))) return [];
29
+ const entries = await fs.readdir(dir);
30
+ const skills: Array<{ name: string; desc: string }> = [];
31
+ for (const name of entries.sort()) {
32
+ const skillMd = path.join(dir, name, 'SKILL.md');
33
+ if (!(await fs.pathExists(skillMd))) continue;
34
+ const content = await fs.readFile(skillMd, 'utf8');
35
+ const m = content.match(/^description:\s*(.+)$/m);
36
+ skills.push({ name, desc: m ? m[1].replace(/^["']|["']$/g, '').trim() : '' });
37
+ }
38
+ return skills;
39
+ }
40
+
41
+ async function readProjectSkillsFromDir(dir: string): Promise<Array<{ name: string; desc: string }>> {
42
+ if (!(await fs.pathExists(dir))) return [];
43
+ const entries = await fs.readdir(dir);
44
+ const skills: Array<{ name: string; desc: string }> = [];
45
+ for (const name of entries.sort()) {
46
+ const readme = path.join(dir, name, 'README.md');
47
+ if (!(await fs.pathExists(readme))) continue;
48
+ const content = await fs.readFile(readme, 'utf8');
49
+ const descLine = content.split('\n').find(line => {
50
+ const trimmed = line.trim();
51
+ return Boolean(trimmed) && !trimmed.startsWith('#') && !trimmed.startsWith('[') && !trimmed.startsWith('<');
52
+ }) || '';
53
+ skills.push({ name, desc: descLine.replace(/[*_`]/g, '').trim() });
54
+ }
55
+ return skills;
56
+ }
57
+
58
+ function resolvePkgRootFallback(): string | null {
59
+ const candidates = [
60
+ path.resolve(__dirname, '../..'),
61
+ path.resolve(__dirname, '../../..'),
62
+ ];
63
+ const match = candidates.find(candidate =>
64
+ fs.existsSync(path.join(candidate, 'skills')) || fs.existsSync(path.join(candidate, 'project-skills'))
65
+ );
66
+ return match || null;
67
+ }
68
+
69
+ function col(s: string, width: number): string {
70
+ return s.length >= width ? s.slice(0, width - 1) + '\u2026' : s.padEnd(width);
71
+ }
72
+
73
+ export function createHelpCommand(): Command {
74
+ return new Command('help')
75
+ .description('Show help information and component catalogue')
76
+ .action(async () => {
77
+ let repoRoot: string;
78
+ try { repoRoot = await findRepoRoot(); } catch { repoRoot = ''; }
79
+ const pkgRoot = resolvePkgRootFallback();
80
+
81
+ const skillsRoot = repoRoot || pkgRoot || '';
82
+ const projectSkillsRoot = repoRoot || pkgRoot || '';
83
+ const skills = skillsRoot ? await readSkillsFromDir(path.join(skillsRoot, 'skills')) : [];
84
+ const projectSkills = projectSkillsRoot ? await readProjectSkillsFromDir(path.join(projectSkillsRoot, 'project-skills')) : [];
85
+
86
+ const W = 80;
87
+ const hr = kleur.dim('-'.repeat(W));
88
+ const section = (title: string) => `\n${kleur.bold().cyan(title)}\n${hr}`;
89
+
90
+ const installSection = [
91
+ section('INSTALL COMMANDS'),
92
+ '',
93
+ ` ${kleur.bold('xtrm install all')}`,
94
+ ` ${kleur.dim('Global install: skills + all hooks (including beads gates) + MCP servers.')}`,
95
+ ` ${kleur.dim('Checks for beads+dolt and prompts to install if missing.')}`,
96
+ '',
97
+ ` ${kleur.bold('xtrm install basic')}`,
98
+ ` ${kleur.dim('Global install: skills + general hooks + MCP servers.')}`,
99
+ ` ${kleur.dim('No beads dependency -- safe to run with zero external deps.')}`,
100
+ '',
101
+ ` ${kleur.bold('xtrm install project')} ${kleur.dim('<tool-name | all>')}`,
102
+ ` ${kleur.dim('Project-scoped install into .claude/ of current git root.')}`,
103
+ ` ${kleur.dim('Run xtrm install project list to see available project skills.')}`,
104
+ '',
105
+ ` ${kleur.dim('Default target directories:')}`,
106
+ ` ${kleur.dim('~/.claude/hooks (global hook scripts)')}`,
107
+ ` ${kleur.dim('~/.claude/skills (global Claude skills)')}`,
108
+ ` ${kleur.dim('~/.agents/skills (agents skills cache mirror)')}`,
109
+ '',
110
+ ` ${kleur.dim('Flags (all profiles): --dry-run --yes / -y --no-mcp --force --prune --backport')}`,
111
+ ].join('\n');
112
+
113
+ const general = HOOK_CATALOG.filter(h => !h.beads && !h.sessionFlow);
114
+ const beads = HOOK_CATALOG.filter(h => h.beads);
115
+ const sessionFlow = HOOK_CATALOG.filter(h => h.sessionFlow);
116
+ const hookRows = (hooks: typeof HOOK_CATALOG) =>
117
+ hooks.map(h =>
118
+ ` ${kleur.white(col(h.file, 34))}${kleur.yellow(col(h.event, 20))}${kleur.dim(h.desc)}`
119
+ ).join('\n');
120
+
121
+ const hooksSection = [
122
+ section('GLOBAL HOOKS'),
123
+ '',
124
+ kleur.dim(' ' + col('File', 34) + col('Event', 20) + 'Description'),
125
+ '',
126
+ hookRows(general),
127
+ '',
128
+ ` ${kleur.dim('beads gate hooks (xtrm install all -- require beads+dolt):')}`,
129
+ hookRows(beads),
130
+ '',
131
+ ` ${kleur.dim('session-flow hooks (xtrm finish lifecycle):')}`,
132
+ hookRows(sessionFlow),
133
+ ].join('\n');
134
+
135
+ const skillRows = skills.map(s => {
136
+ const desc = s.desc.length > 46 ? s.desc.slice(0, 45) + '\u2026' : s.desc;
137
+ return ` ${kleur.white(col(s.name, 30))}${kleur.dim(desc)}`;
138
+ }).join('\n');
139
+
140
+ const skillsSection = [
141
+ section(`SKILLS ${kleur.dim('(' + skills.length + ' available)')}`),
142
+ '',
143
+ skills.length ? skillRows : kleur.dim(' (none found -- run from repo root to see skills)'),
144
+ ].join('\n');
145
+
146
+ const psRows = projectSkills.map(s =>
147
+ ` ${kleur.white(col(s.name, 30))}${kleur.dim(s.desc)}`
148
+ ).join('\n');
149
+
150
+ const psSection = [
151
+ section('PROJECT SKILLS + HOOKS'),
152
+ '',
153
+ projectSkills.length ? psRows : kleur.dim(' (none found in package)'),
154
+ '',
155
+ ` ${kleur.dim('Install: xtrm install project <name> | xtrm install project list')}`,
156
+ ` ${kleur.dim('Each project skill can install .claude/skills plus project hooks/settings.')}`,
157
+ ].join('\n');
158
+
159
+ const otherSection = [
160
+ section('OTHER COMMANDS'),
161
+ '',
162
+ ` ${kleur.bold('xtrm status')} ${kleur.dim('Show pending changes without applying them')}`,
163
+ ` ${kleur.bold('xtrm finish')} ${kleur.dim('Run blocking session closure lifecycle (PR + cleanup)')}`,
164
+ ` ${kleur.bold('xtrm reset')} ${kleur.dim('Clear saved preferences and start fresh')}`,
165
+ ` ${kleur.bold('xtrm help')} ${kleur.dim('Show this overview')}`,
166
+ ].join('\n');
167
+
168
+ const resourcesSection = [
169
+ section('RESOURCES'),
170
+ '',
171
+ ` Repository https://github.com/Jaggerxtrm/xtrm-tools`,
172
+ ` Issues https://github.com/Jaggerxtrm/xtrm-tools/issues`,
173
+ '',
174
+ ` ${kleur.dim("Run 'xtrm <command> --help' for command-specific options.")}`,
175
+ '',
176
+ ].join('\n');
177
+
178
+ console.log([installSection, hooksSection, skillsSection, psSection, otherSection, resourcesSection].join('\n'));
179
+ });
180
+ }