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|mcp__serena__rename_symbol|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol)
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 service-skills-set # Docker service expertise
241
- xtrm install project tdd-guard # TDD enforcement
242
- xtrm install project ts-quality-gate # TypeScript quality
243
- xtrm install project py-quality-gate # Python quality
244
- xtrm install project all # Install every available project skill
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 quick reference for this project
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 |
@@ -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 mergeMatcher = (existingMatcher, incomingMatcher) => {
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 = mergeMatcher(existingHook.matcher, incomingHook.matcher);
41046
+ existingHook.matcher = mergeMatcher2(existingHook.matcher, incomingHook.matcher);
40986
41047
  }
40987
41048
  }
40988
41049
  result.hooks[event] = mergedEventHooks;