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.
- package/.claude-plugin/plugin.json +1 -1
- package/cli/dist/index.cjs +530 -74
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/config/pi/extensions/xtrm-ui/index.ts +1 -1
- package/hooks/tsconfig-cache.json +2 -12
- package/package.json +1 -1
- package/plugins/xtrm-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/xtrm-tools/hooks/tsconfig-cache.json +2 -12
package/cli/dist/index.cjs
CHANGED
|
@@ -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
|
-
|
|
34842
|
-
|
|
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
|
|
35398
|
-
|
|
35399
|
-
|
|
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
|
|
35462
|
-
|
|
35463
|
-
|
|
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
|
|
35496
|
-
|
|
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
|
|
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
|
|
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)(
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
54947
|
+
var import_node_child_process7 = require("child_process");
|
|
54492
54948
|
function git(args, cwd) {
|
|
54493
|
-
const r = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
55700
|
+
var import_node_child_process10 = require("child_process");
|
|
55245
55701
|
function isGhAvailable() {
|
|
55246
|
-
return (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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|