xtrm-tools 2.1.8 → 2.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ Task intake and service routing for Docker service projects.
|
|
|
174
174
|
### Skill-Associated Hooks
|
|
175
175
|
|
|
176
176
|
**skill-suggestion.py**
|
|
177
|
-
- Skills: `prompt-improving`, `delegating`
|
|
177
|
+
- Skills: `prompt-improving`, `delegating`, `using-quality-gates`
|
|
178
178
|
- Trigger: UserPromptSubmit
|
|
179
179
|
- Purpose: Proactive skill suggestions based on prompt analysis
|
|
180
180
|
- Config: `settings.json` → `skillSuggestions.enabled: true`
|
|
@@ -196,11 +196,20 @@ Task intake and service routing for Docker service projects.
|
|
|
196
196
|
- Purpose: Enriches tool calls with knowledge graph context via `gitnexus augment`
|
|
197
197
|
- Config: Auto-wired in `settings.json`
|
|
198
198
|
|
|
199
|
+
**gitnexus-impact-reminder.py**
|
|
200
|
+
- Trigger: UserPromptSubmit
|
|
201
|
+
- Purpose: Reminds to run impact analysis before editing code symbols
|
|
202
|
+
- Config: Auto-wired in `settings.json`
|
|
203
|
+
|
|
199
204
|
### Standalone Hooks
|
|
200
205
|
|
|
201
206
|
**main-guard.mjs**
|
|
202
|
-
- Trigger: PreToolUse (Write|Edit|MultiEdit|
|
|
203
|
-
- Purpose: Blocks direct edits on protected branches with structured deny output
|
|
207
|
+
- Trigger: PreToolUse (Write|Edit|MultiEdit|Serena edit tools)
|
|
208
|
+
- Purpose: Blocks direct edits on protected branches (main/master) with structured deny output
|
|
209
|
+
|
|
210
|
+
**main-guard-post-push.mjs**
|
|
211
|
+
- Trigger: PostToolUse (Bash: git push)
|
|
212
|
+
- Purpose: After pushing feature branch, reminds to open PR, merge, and sync local
|
|
204
213
|
|
|
205
214
|
**type-safety-enforcement.py**
|
|
206
215
|
- Trigger: PreToolUse (Bash|Edit|Write)
|
|
@@ -211,10 +220,13 @@ Task intake and service routing for Docker service projects.
|
|
|
211
220
|
- Purpose: Shared hook input/output helper
|
|
212
221
|
|
|
213
222
|
**beads gate hooks** (installed with `xtrm install all`, or included when beads+dolt is available):
|
|
214
|
-
- `beads-edit-gate.mjs` (PreToolUse)
|
|
215
|
-
- `beads-commit-gate.mjs` (PreToolUse)
|
|
216
|
-
- `beads-stop-gate.mjs` (Stop)
|
|
217
|
-
- `beads-close-memory-prompt.mjs` (PostToolUse)
|
|
223
|
+
- `beads-edit-gate.mjs` (PreToolUse) — Blocks writes without active issue claim
|
|
224
|
+
- `beads-commit-gate.mjs` (PreToolUse) — Blocks commits with unresolved session claim
|
|
225
|
+
- `beads-stop-gate.mjs` (Stop) — Blocks session stop while claim remains open
|
|
226
|
+
- `beads-close-memory-prompt.mjs` (PostToolUse) — Prompts memory handoff after `bd close`
|
|
227
|
+
|
|
228
|
+
### PostToolUse Hooks
|
|
229
|
+
- `main-guard-post-push.mjs` — After feature-branch push, reminds PR/merge/sync steps
|
|
218
230
|
|
|
219
231
|
## Project Skills
|
|
220
232
|
|
|
@@ -224,10 +236,9 @@ Task intake and service routing for Docker service projects.
|
|
|
224
236
|
|
|
225
237
|
| Skill | Description | Hook Type |
|
|
226
238
|
|-------|-------------|-----------|
|
|
239
|
+
| `quality-gates` | Unified PostToolUse code quality hooks — runs linting, type checking, and formatting on every edit | PostToolUse |
|
|
227
240
|
| `service-skills-set` | Docker service expertise — gives Claude persistent knowledge about your services | SessionStart, PreToolUse, PostToolUse |
|
|
228
241
|
| `tdd-guard` | Enforce Test-Driven Development — blocks implementation until failing tests exist | SessionStart, PreToolUse, UserPromptSubmit |
|
|
229
|
-
| `ts-quality-gate` | TypeScript/ESLint/Prettier quality gate — runs on every edit, auto-fixes issues | PostToolUse |
|
|
230
|
-
| `py-quality-gate` | Python ruff/mypy quality gate — linting, formatting, and type checking | PostToolUse |
|
|
231
242
|
|
|
232
243
|
### Installing Project Skills
|
|
233
244
|
|
|
@@ -237,12 +248,11 @@ xtrm install project list
|
|
|
237
248
|
|
|
238
249
|
# Install a specific skill into your current project
|
|
239
250
|
cd my-project
|
|
240
|
-
xtrm install project
|
|
241
|
-
xtrm install project
|
|
242
|
-
xtrm install project
|
|
243
|
-
xtrm install project
|
|
244
|
-
xtrm install project
|
|
245
|
-
xtrm install project '*' # Same as above; quote to avoid shell expansion
|
|
251
|
+
xtrm install project quality-gates # Unified quality gates (Python + TypeScript)
|
|
252
|
+
xtrm install project service-skills-set # Docker service expertise
|
|
253
|
+
xtrm install project tdd-guard # TDD enforcement
|
|
254
|
+
xtrm install project all # Install every available project skill
|
|
255
|
+
xtrm install project '*' # Same as above; quote to avoid shell expansion
|
|
246
256
|
```
|
|
247
257
|
|
|
248
258
|
**Note:** Project skills install Claude hooks and skills into your project's `.claude/` directory. Some skills require additional manual setup (e.g., installing npm packages or Python dependencies). Always read the documentation at `.claude/docs/<skill>-readme.md` after installation.
|
|
@@ -628,10 +638,16 @@ Once registered, skills activate automatically when Claude:
|
|
|
628
638
|
## Documentation
|
|
629
639
|
|
|
630
640
|
### Core Documentation
|
|
641
|
+
- [README.md](README.md) - Main documentation and quick start
|
|
631
642
|
- [CHANGELOG.md](CHANGELOG.md) - Version history and breaking changes
|
|
632
643
|
- [ROADMAP.md](ROADMAP.md) - Future enhancements and planned features
|
|
633
|
-
- [AGENTS.md](AGENTS.md) - GitNexus
|
|
644
|
+
- [AGENTS.md](AGENTS.md) - GitNexus + bd (beads) quick reference
|
|
634
645
|
- [CLAUDE.md](CLAUDE.md) - Claude Code development guide
|
|
646
|
+
- [hooks.md](hooks.md) - Global hooks module reference
|
|
647
|
+
- [skills.md](skills.md) - Global skills catalog
|
|
648
|
+
- [project-skills.md](project-skills.md) - Project-local skills reference
|
|
649
|
+
- [mcp.md](mcp.md) - MCP servers configuration
|
|
650
|
+
- [testing.md](testing.md) - Production live testing checklist
|
|
635
651
|
|
|
636
652
|
### Skill Documentation
|
|
637
653
|
- [skills/prompt-improving/README.md](skills/prompt-improving/README.md) - Prompt improvement skill
|
|
@@ -653,6 +669,10 @@ Once registered, skills activate automatically when Claude:
|
|
|
653
669
|
|
|
654
670
|
| Version | Date | Highlights |
|
|
655
671
|
| ------- | ---------- | -------------------------------------------------- |
|
|
672
|
+
| 2.1.9 | 2026-03-15 | Main-guard post-push hook, quality-gates unified, gitnexus impact enforcement |
|
|
673
|
+
| 2.1.8 | 2026-03-13 | Beads gate hooks hardening, service skills trinity updates |
|
|
674
|
+
| 2.1.7 | 2026-03-12 | GitNexus impact analysis hook, onboarding improvements |
|
|
675
|
+
| 2.1.0 | 2026-03-12 | Project skills engine, Claude Code-only focus, CLI rebrand |
|
|
656
676
|
| 1.7.0 | 2026-02-25 | GitNexus integration, unified 3-phase sync, MCP CLI sync, env management |
|
|
657
677
|
| 1.6.0 | 2026-02-24 | Documenting skill hardening (drift detection, INDEX blocks) |
|
|
658
678
|
| 1.5.0 | 2026-02-23 | Service Skills Set (Trinity), git hooks, auto-activation |
|
package/cli/dist/index.cjs
CHANGED
|
@@ -37306,10 +37306,71 @@ function isValueProtected(keyPath) {
|
|
|
37306
37306
|
(protectedPath) => keyPath === protectedPath || keyPath.startsWith(protectedPath + ".")
|
|
37307
37307
|
);
|
|
37308
37308
|
}
|
|
37309
|
+
function extractHookCommands(wrapper) {
|
|
37310
|
+
if (!wrapper || !Array.isArray(wrapper.hooks)) return [];
|
|
37311
|
+
return wrapper.hooks.map((h) => h?.command).filter((c) => typeof c === "string" && c.trim().length > 0);
|
|
37312
|
+
}
|
|
37313
|
+
function commandKey(command) {
|
|
37314
|
+
const m = command.match(/([A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))(?!.*[A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))/);
|
|
37315
|
+
return m?.[1] || command.trim();
|
|
37316
|
+
}
|
|
37317
|
+
function mergeMatcher(existingMatcher, incomingMatcher) {
|
|
37318
|
+
const parts = [
|
|
37319
|
+
...existingMatcher.split("|").map((s) => s.trim()),
|
|
37320
|
+
...incomingMatcher.split("|").map((s) => s.trim())
|
|
37321
|
+
].filter(Boolean);
|
|
37322
|
+
return Array.from(new Set(parts)).join("|");
|
|
37323
|
+
}
|
|
37324
|
+
function mergeHookWrappers(existing, incoming) {
|
|
37325
|
+
const merged = existing.map((w) => ({ ...w }));
|
|
37326
|
+
for (const incomingWrapper of incoming) {
|
|
37327
|
+
const incomingCommands = extractHookCommands(incomingWrapper);
|
|
37328
|
+
if (incomingCommands.length === 0) {
|
|
37329
|
+
merged.push(incomingWrapper);
|
|
37330
|
+
continue;
|
|
37331
|
+
}
|
|
37332
|
+
const incomingKeys = new Set(incomingCommands.map(commandKey));
|
|
37333
|
+
const existingIndex = merged.findIndex((existingWrapper2) => {
|
|
37334
|
+
const existingCommands = extractHookCommands(existingWrapper2);
|
|
37335
|
+
return existingCommands.some((c) => incomingKeys.has(commandKey(c)));
|
|
37336
|
+
});
|
|
37337
|
+
if (existingIndex === -1) {
|
|
37338
|
+
merged.push(incomingWrapper);
|
|
37339
|
+
continue;
|
|
37340
|
+
}
|
|
37341
|
+
const existingWrapper = merged[existingIndex];
|
|
37342
|
+
if (typeof existingWrapper.matcher === "string" && typeof incomingWrapper.matcher === "string") {
|
|
37343
|
+
existingWrapper.matcher = mergeMatcher(existingWrapper.matcher, incomingWrapper.matcher);
|
|
37344
|
+
}
|
|
37345
|
+
if (Array.isArray(existingWrapper.hooks) && Array.isArray(incomingWrapper.hooks)) {
|
|
37346
|
+
const existingByKey = new Set(existingWrapper.hooks.map((h) => h?.command).filter((c) => typeof c === "string").map(commandKey));
|
|
37347
|
+
for (const hook of incomingWrapper.hooks) {
|
|
37348
|
+
const cmd = hook?.command;
|
|
37349
|
+
if (typeof cmd !== "string" || !existingByKey.has(commandKey(cmd))) {
|
|
37350
|
+
existingWrapper.hooks.push(hook);
|
|
37351
|
+
}
|
|
37352
|
+
}
|
|
37353
|
+
}
|
|
37354
|
+
}
|
|
37355
|
+
return merged;
|
|
37356
|
+
}
|
|
37357
|
+
function mergeHooksObject(existingHooks, incomingHooks) {
|
|
37358
|
+
const result = { ...existingHooks || {} };
|
|
37359
|
+
for (const [event, incomingWrappers] of Object.entries(incomingHooks || {})) {
|
|
37360
|
+
const existingWrappers = Array.isArray(result[event]) ? result[event] : [];
|
|
37361
|
+
const incomingArray = Array.isArray(incomingWrappers) ? incomingWrappers : [];
|
|
37362
|
+
result[event] = mergeHookWrappers(existingWrappers, incomingArray);
|
|
37363
|
+
}
|
|
37364
|
+
return result;
|
|
37365
|
+
}
|
|
37309
37366
|
function deepMergeWithProtection(original, updates, currentPath = "") {
|
|
37310
37367
|
const result = { ...original };
|
|
37311
37368
|
for (const [key, value] of Object.entries(updates)) {
|
|
37312
37369
|
const keyPath = currentPath ? `${currentPath}.${key}` : key;
|
|
37370
|
+
if (key === "hooks" && typeof value === "object" && value !== null && typeof original[key] === "object" && original[key] !== null) {
|
|
37371
|
+
result[key] = mergeHooksObject(original[key], value);
|
|
37372
|
+
continue;
|
|
37373
|
+
}
|
|
37313
37374
|
if (isValueProtected(keyPath) && original.hasOwnProperty(key)) {
|
|
37314
37375
|
continue;
|
|
37315
37376
|
}
|
|
@@ -40953,7 +41014,7 @@ function deepMergeHooks(existing, incoming) {
|
|
|
40953
41014
|
const m = cmd.match(/([A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))(?!.*[A-Za-z0-9._-]+\.(?:py|cjs|mjs|js))/);
|
|
40954
41015
|
return m?.[1] ?? null;
|
|
40955
41016
|
};
|
|
40956
|
-
const
|
|
41017
|
+
const mergeMatcher2 = (existingMatcher, incomingMatcher) => {
|
|
40957
41018
|
const existingParts = existingMatcher.split("|").map((s) => s.trim()).filter(Boolean);
|
|
40958
41019
|
const incomingParts = incomingMatcher.split("|").map((s) => s.trim()).filter(Boolean);
|
|
40959
41020
|
const merged = [...existingParts];
|
|
@@ -40982,7 +41043,7 @@ function deepMergeHooks(existing, incoming) {
|
|
|
40982
41043
|
}
|
|
40983
41044
|
const existingHook = mergedEventHooks[existingIndex];
|
|
40984
41045
|
if (typeof existingHook.matcher === "string" && typeof incomingHook.matcher === "string") {
|
|
40985
|
-
existingHook.matcher =
|
|
41046
|
+
existingHook.matcher = mergeMatcher2(existingHook.matcher, incomingHook.matcher);
|
|
40986
41047
|
}
|
|
40987
41048
|
}
|
|
40988
41049
|
result.hooks[event] = mergedEventHooks;
|