xtrm-tools 2.1.7 → 2.1.9
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/cli/dist/index.cjs +144 -21
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/hooks/gitnexus-impact-reminder.py +35 -0
- package/package.json +1 -1
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;
|
|
@@ -41134,35 +41195,55 @@ Installing ${skills.length} project skills:
|
|
|
41134
41195
|
}
|
|
41135
41196
|
function buildProjectInitGuide() {
|
|
41136
41197
|
const lines = [
|
|
41137
|
-
kleur_default.bold("\nProject Init \u2014
|
|
41138
|
-
|
|
41198
|
+
kleur_default.bold("\nProject Init \u2014 Production baseline\n"),
|
|
41199
|
+
kleur_default.dim("This command prints a complete setup checklist and then bootstraps beads + GitNexus for this repo.\n"),
|
|
41200
|
+
`${kleur_default.cyan("1) Install core project enforcement (quality-gates):")}`,
|
|
41139
41201
|
kleur_default.dim(" xtrm install project quality-gates"),
|
|
41202
|
+
kleur_default.dim(" - Installs TypeScript + Python PostToolUse quality hooks"),
|
|
41203
|
+
kleur_default.dim(" - Includes Serena matcher coverage for edit-equivalent tools"),
|
|
41204
|
+
kleur_default.dim(" - Enforces linting/type-checking based on your repo config"),
|
|
41140
41205
|
"",
|
|
41141
|
-
`${kleur_default.cyan("2)
|
|
41142
|
-
kleur_default.dim(" - Testing: commands should run and fail when behavior regresses"),
|
|
41143
|
-
kleur_default.dim(" - Linting/formatting: ESLint+Prettier (TS) or ruff (Python)"),
|
|
41144
|
-
kleur_default.dim(" - Type checks: tsc (TS) or mypy/pyright (Python)"),
|
|
41145
|
-
kleur_default.dim(" - Hooks only enforce what your project config defines"),
|
|
41146
|
-
"",
|
|
41147
|
-
`${kleur_default.cyan("3) Optional: TDD Guard (standalone, for test-first enforcement)")}`,
|
|
41206
|
+
`${kleur_default.cyan("2) Optional installs depending on workflow:")}`,
|
|
41148
41207
|
kleur_default.dim(" xtrm install project tdd-guard"),
|
|
41149
|
-
kleur_default.dim(" -
|
|
41208
|
+
kleur_default.dim(" - Strong test-first enforcement (PreToolUse + prompt/session checks)"),
|
|
41209
|
+
kleur_default.dim(" xtrm install project service-skills-set"),
|
|
41210
|
+
kleur_default.dim(" - Service-aware skill routing + drift checks + git hook reminders"),
|
|
41211
|
+
"",
|
|
41212
|
+
`${kleur_default.cyan("3) Configure repo checks (hooks only enforce what your repo defines):")}`,
|
|
41213
|
+
kleur_default.dim(" - Tests: should fail on regressions (required for TDD workflows)"),
|
|
41214
|
+
kleur_default.dim(" - TS: eslint + prettier + tsc"),
|
|
41215
|
+
kleur_default.dim(" - PY: ruff + mypy/pyright"),
|
|
41216
|
+
"",
|
|
41217
|
+
`${kleur_default.cyan("4) Beads workflow (required for gated edit/commit flow):")}`,
|
|
41218
|
+
kleur_default.dim(" - Claim work: bd ready --json -> bd update <id> --claim --json"),
|
|
41219
|
+
kleur_default.dim(" - During work: keep issue status current; create discovered follow-ups"),
|
|
41220
|
+
kleur_default.dim(' - Finish work: bd close <id> --reason "Done" --json'),
|
|
41221
|
+
"",
|
|
41222
|
+
`${kleur_default.cyan("5) Git workflow (main-guard expected path):")}`,
|
|
41223
|
+
kleur_default.dim(" - git checkout -b feature/<name>"),
|
|
41224
|
+
kleur_default.dim(" - commit on feature branch only"),
|
|
41225
|
+
kleur_default.dim(" - git push -u origin feature/<name>"),
|
|
41226
|
+
kleur_default.dim(" - gh pr create --fill && gh pr merge --squash"),
|
|
41227
|
+
kleur_default.dim(" - git checkout main && git pull --ff-only"),
|
|
41150
41228
|
"",
|
|
41151
|
-
`${kleur_default.cyan("
|
|
41152
|
-
kleur_default.dim(" -
|
|
41153
|
-
kleur_default.dim(" -
|
|
41154
|
-
kleur_default.dim(" -
|
|
41229
|
+
`${kleur_default.cyan("6) Hooks and startup automation:")}`,
|
|
41230
|
+
kleur_default.dim(" - PreToolUse: safety gates (main/beads/TDD/type-safety/etc.)"),
|
|
41231
|
+
kleur_default.dim(" - PostToolUse: quality checks + reminders"),
|
|
41232
|
+
kleur_default.dim(" - Stop: beads stop-gate prevents unresolved session claims"),
|
|
41233
|
+
kleur_default.dim(" - SessionStart: workflow context reminders"),
|
|
41155
41234
|
"",
|
|
41156
41235
|
kleur_default.bold("Quick start commands:"),
|
|
41157
41236
|
kleur_default.dim(" xtrm install project list"),
|
|
41158
41237
|
kleur_default.dim(" xtrm install project quality-gates"),
|
|
41238
|
+
kleur_default.dim(" xtrm install project tdd-guard"),
|
|
41239
|
+
kleur_default.dim(" xtrm install project service-skills-set"),
|
|
41159
41240
|
""
|
|
41160
41241
|
];
|
|
41161
41242
|
return lines.join("\n");
|
|
41162
41243
|
}
|
|
41163
41244
|
async function printProjectInitGuide() {
|
|
41164
41245
|
console.log(buildProjectInitGuide());
|
|
41165
|
-
await
|
|
41246
|
+
await bootstrapProjectInit();
|
|
41166
41247
|
}
|
|
41167
41248
|
async function installProjectByName(toolName) {
|
|
41168
41249
|
if (toolName === "all" || toolName === "*") {
|
|
@@ -41171,16 +41252,20 @@ async function installProjectByName(toolName) {
|
|
|
41171
41252
|
}
|
|
41172
41253
|
await installProjectSkill(toolName);
|
|
41173
41254
|
}
|
|
41174
|
-
async function
|
|
41255
|
+
async function bootstrapProjectInit() {
|
|
41175
41256
|
let projectRoot;
|
|
41176
41257
|
try {
|
|
41177
41258
|
projectRoot = getProjectRoot();
|
|
41178
41259
|
} catch (err) {
|
|
41179
41260
|
console.log(kleur_default.yellow(`
|
|
41180
|
-
\u26A0 Skipping
|
|
41261
|
+
\u26A0 Skipping project bootstrap: ${err.message}
|
|
41181
41262
|
`));
|
|
41182
41263
|
return;
|
|
41183
41264
|
}
|
|
41265
|
+
await runBdInitForProject(projectRoot);
|
|
41266
|
+
await runGitNexusInitForProject(projectRoot);
|
|
41267
|
+
}
|
|
41268
|
+
async function runBdInitForProject(projectRoot) {
|
|
41184
41269
|
console.log(kleur_default.bold("Running beads initialization (bd init)..."));
|
|
41185
41270
|
const result = (0, import_child_process3.spawnSync)("bd", ["init"], {
|
|
41186
41271
|
cwd: projectRoot,
|
|
@@ -41206,6 +41291,44 @@ ${result.stderr || ""}`.toLowerCase();
|
|
|
41206
41291
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
41207
41292
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
41208
41293
|
}
|
|
41294
|
+
async function runGitNexusInitForProject(projectRoot) {
|
|
41295
|
+
const gitnexusCheck = (0, import_child_process3.spawnSync)("gitnexus", ["--version"], {
|
|
41296
|
+
cwd: projectRoot,
|
|
41297
|
+
encoding: "utf8",
|
|
41298
|
+
timeout: 5e3
|
|
41299
|
+
});
|
|
41300
|
+
if (gitnexusCheck.status !== 0) {
|
|
41301
|
+
console.log(kleur_default.yellow(" \u26A0 gitnexus not found; skipping index bootstrap"));
|
|
41302
|
+
console.log(kleur_default.dim(" Install with: npm install -g gitnexus"));
|
|
41303
|
+
return;
|
|
41304
|
+
}
|
|
41305
|
+
console.log(kleur_default.bold("Checking GitNexus index status..."));
|
|
41306
|
+
const status = (0, import_child_process3.spawnSync)("gitnexus", ["status"], {
|
|
41307
|
+
cwd: projectRoot,
|
|
41308
|
+
encoding: "utf8",
|
|
41309
|
+
timeout: 1e4
|
|
41310
|
+
});
|
|
41311
|
+
const statusText = `${status.stdout || ""}
|
|
41312
|
+
${status.stderr || ""}`.toLowerCase();
|
|
41313
|
+
const needsAnalyze = status.status !== 0 || statusText.includes("stale") || statusText.includes("not indexed") || statusText.includes("missing");
|
|
41314
|
+
if (!needsAnalyze) {
|
|
41315
|
+
console.log(kleur_default.dim(" \u2713 GitNexus index is ready"));
|
|
41316
|
+
return;
|
|
41317
|
+
}
|
|
41318
|
+
console.log(kleur_default.bold("Running GitNexus indexing (gitnexus analyze)..."));
|
|
41319
|
+
const analyze = (0, import_child_process3.spawnSync)("gitnexus", ["analyze"], {
|
|
41320
|
+
cwd: projectRoot,
|
|
41321
|
+
encoding: "utf8",
|
|
41322
|
+
timeout: 12e4
|
|
41323
|
+
});
|
|
41324
|
+
if (analyze.status === 0) {
|
|
41325
|
+
console.log(kleur_default.green(" \u2713 GitNexus index updated"));
|
|
41326
|
+
return;
|
|
41327
|
+
}
|
|
41328
|
+
if (analyze.stdout) process.stdout.write(analyze.stdout);
|
|
41329
|
+
if (analyze.stderr) process.stderr.write(analyze.stderr);
|
|
41330
|
+
console.log(kleur_default.yellow(` \u26A0 gitnexus analyze exited with code ${analyze.status}`));
|
|
41331
|
+
}
|
|
41209
41332
|
async function listProjectSkills() {
|
|
41210
41333
|
const entries = await getAvailableProjectSkills();
|
|
41211
41334
|
if (entries.length === 0) {
|
|
@@ -41269,7 +41392,7 @@ function createInstallProjectCommand() {
|
|
|
41269
41392
|
const listCmd = new Command("list").description("List available project skills").action(async () => {
|
|
41270
41393
|
await listProjectSkills();
|
|
41271
41394
|
});
|
|
41272
|
-
const initCmd = new Command("init").description("Show
|
|
41395
|
+
const initCmd = new Command("init").description("Show full onboarding guidance and bootstrap beads + GitNexus").action(async () => {
|
|
41273
41396
|
await printProjectInitGuide();
|
|
41274
41397
|
});
|
|
41275
41398
|
installProjectCmd.addCommand(listCmd);
|
|
@@ -41278,7 +41401,7 @@ function createInstallProjectCommand() {
|
|
|
41278
41401
|
}
|
|
41279
41402
|
function createProjectCommand() {
|
|
41280
41403
|
const projectCmd = new Command("project").description("Project skill onboarding and installation helpers");
|
|
41281
|
-
projectCmd.command("init").description("Show
|
|
41404
|
+
projectCmd.command("init").description("Show full onboarding guidance and bootstrap beads + GitNexus").action(async () => {
|
|
41282
41405
|
await printProjectInitGuide();
|
|
41283
41406
|
});
|
|
41284
41407
|
projectCmd.command("list").description("List available project skills").action(async () => {
|