@makerx/yoke 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +132 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -30,6 +30,7 @@ yoke sync --user # ~/.claude/skills
30
30
 
31
31
  ```bash
32
32
  yoke sync [--user] [--force] # write yoke's skills
33
+ yoke update # update yoke and re-sync its skills where they were synced
33
34
  yoke review [number] # review a PR (used by /review)
34
35
  yoke review-ci # add the CI review workflow (used by /review-ci)
35
36
  yoke review-ci-gate # require the review / gate check on the default branch (used by /review-ci-gate)
package/dist/cli.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { relative } from "path";
4
+ import { relative as relative2 } from "path";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@makerx/yoke",
10
- version: "0.14.0",
10
+ version: "0.16.0",
11
11
  description: "Lean, user-invoked Claude Code skills for MakerX AI-assisted delivery, plus the CLI they call.",
12
12
  license: "MIT",
13
13
  author: "MakerX",
@@ -486,9 +486,9 @@ var ReviewError = class extends Error {
486
486
  }
487
487
  };
488
488
  var maxBuffer = 256 * 1024 * 1024;
489
- var run = (command, args, cwd, input) => new Promise((resolve, reject) => {
489
+ var run = (command, args, cwd, input) => new Promise((resolve3, reject) => {
490
490
  const child = execFile(command, args, { cwd, encoding: "utf8", maxBuffer }, (error, stdout, stderr) => {
491
- if (!error) return resolve(stdout);
491
+ if (!error) return resolve3(stdout);
492
492
  const detail = stderr.trim();
493
493
  reject(new ReviewError(`\`${command} ${args.join(" ")}\` failed${detail ? `: ${detail}` : ""}`));
494
494
  });
@@ -1904,7 +1904,7 @@ function exitError(harness, code, output) {
1904
1904
  function hasReview(path) {
1905
1905
  return existsSync3(path) && readFileSync4(path, "utf8").trim().length > 0;
1906
1906
  }
1907
- var runHarness = (run2) => new Promise((resolve) => {
1907
+ var runHarness = (run2) => new Promise((resolve3) => {
1908
1908
  const shell = process.platform === "win32";
1909
1909
  const args = harnessArgs(run2);
1910
1910
  const child = spawn(run2.harness, shell ? args.map(windowsQuote) : args, {
@@ -1941,14 +1941,14 @@ var runHarness = (run2) => new Promise((resolve) => {
1941
1941
  child.stdin.on("error", () => {
1942
1942
  });
1943
1943
  child.stdin.end(run2.prompt);
1944
- child.on("error", (error) => resolve({ ok: false, error: spawnError(run2.harness, error) }));
1944
+ child.on("error", (error) => resolve3({ ok: false, error: spawnError(run2.harness, error) }));
1945
1945
  child.on("close", (code) => {
1946
1946
  onLine(pending);
1947
1947
  if (code !== 0)
1948
- return resolve({ ok: false, error: exitError(run2.harness, code, [stderr || finalText, lastError].filter(Boolean).join("\n")) });
1948
+ return resolve3({ ok: false, error: exitError(run2.harness, code, [stderr || finalText, lastError].filter(Boolean).join("\n")) });
1949
1949
  if (run2.harness === "claude") writeFileSync3(run2.outputPath, finalText);
1950
- if (!hasReview(run2.outputPath)) return resolve({ ok: false, error: `${run2.harness} returned an empty review` });
1951
- resolve({ ok: true, ...model ? { model } : {}, ...usage ? { usage } : {} });
1950
+ if (!hasReview(run2.outputPath)) return resolve3({ ok: false, error: `${run2.harness} returned an empty review` });
1951
+ resolve3({ ok: true, ...model ? { model } : {}, ...usage ? { usage } : {} });
1952
1952
  });
1953
1953
  });
1954
1954
 
@@ -2288,9 +2288,9 @@ var commandTimeout = 3e4;
2288
2288
  var probeTimeout = 6e4;
2289
2289
  var maxBodyChars2 = 300;
2290
2290
  function exec(harness, args) {
2291
- return new Promise((resolve) => {
2291
+ return new Promise((resolve3) => {
2292
2292
  const options = { encoding: "utf8", timeout: commandTimeout, shell: process.platform === "win32" };
2293
- execFile2(harness, args, options, (error, stdout) => resolve({ ok: !error, stdout }));
2293
+ execFile2(harness, args, options, (error, stdout) => resolve3({ ok: !error, stdout }));
2294
2294
  });
2295
2295
  }
2296
2296
  async function claudeSignedIn() {
@@ -2621,17 +2621,137 @@ function syncSkills(destination, userDestination, force = false, skills = bundle
2621
2621
  return result;
2622
2622
  }
2623
2623
 
2624
+ // src/features/update/install.ts
2625
+ import { execSync } from "child_process";
2626
+ import { dirname as dirname3, isAbsolute, relative, resolve } from "path";
2627
+ import { fileURLToPath } from "url";
2628
+ function installDir(moduleUrl = import.meta.url) {
2629
+ return resolve(dirname3(fileURLToPath(moduleUrl)), "..");
2630
+ }
2631
+ function npmGlobalRoot() {
2632
+ return execSync("npm root -g", { stdio: "pipe" }).toString().trim();
2633
+ }
2634
+ function gitToplevel(dir) {
2635
+ return execSync("git rev-parse --show-toplevel", { cwd: dir, stdio: "pipe" }).toString().trim();
2636
+ }
2637
+ function isGitClone(dir, toplevel = gitToplevel) {
2638
+ try {
2639
+ return resolve(toplevel(dir)) === resolve(dir);
2640
+ } catch {
2641
+ return false;
2642
+ }
2643
+ }
2644
+ function isInside(parent, child, platform = process.platform) {
2645
+ const fold = (path) => platform === "win32" ? resolve(path).toLowerCase() : resolve(path);
2646
+ const rel = relative(fold(parent), fold(child));
2647
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
2648
+ }
2649
+ function isGlobalNpmInstall(dir, globalRoot = npmGlobalRoot, platform = process.platform) {
2650
+ try {
2651
+ return isInside(globalRoot(), dir, platform);
2652
+ } catch {
2653
+ return false;
2654
+ }
2655
+ }
2656
+ function detectInstall(dir, globalRoot = npmGlobalRoot, toplevel = gitToplevel) {
2657
+ if (isGitClone(dir, toplevel)) return "git";
2658
+ return isGlobalNpmInstall(dir, globalRoot) ? "npm" : "unknown";
2659
+ }
2660
+
2661
+ // src/features/update/targets.ts
2662
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
2663
+ import { homedir as homedir4 } from "os";
2664
+ import { join as join10, resolve as resolve2 } from "path";
2665
+ function holdsYokeSkill(root, skills) {
2666
+ return skills.some((skill) => {
2667
+ if (skill.userLevel) return false;
2668
+ const file = join10(root, skill.name, "SKILL.md");
2669
+ return existsSync9(file) && readFileSync10(file, "utf8").includes(ownerMarker);
2670
+ });
2671
+ }
2672
+ function syncTargets(cwd = process.cwd(), home = homedir4(), skills = bundledSkills) {
2673
+ const userRoot = skillsRoot(true, cwd, home);
2674
+ const repoRoot2 = skillsRoot(false, cwd, home);
2675
+ const targets = [];
2676
+ if (holdsYokeSkill(userRoot, skills)) targets.push("user");
2677
+ if (resolve2(repoRoot2) !== resolve2(userRoot) && holdsYokeSkill(repoRoot2, skills)) targets.push("repo");
2678
+ return targets;
2679
+ }
2680
+
2681
+ // src/features/update/update.ts
2682
+ import { execSync as execSync2 } from "child_process";
2683
+ var packageName = "@makerx/yoke";
2684
+ var syncCommands = { user: "yoke sync --user", repo: "yoke sync" };
2685
+ var noTargetsMessage = "No yoke skills were synced here before, so none were re-synced. Run `yoke sync` for this repo's .claude/skills, or `yoke sync --user` for ~/.claude/skills.";
2686
+ function runInherited(command, cwd) {
2687
+ execSync2(command, { cwd, stdio: "inherit" });
2688
+ }
2689
+ function installLatest(install, dir, run2, log) {
2690
+ if (install === "npm") {
2691
+ log("Updating the global npm install...");
2692
+ run2(`npm i -g ${packageName}@latest`);
2693
+ return;
2694
+ }
2695
+ log("Pulling the latest yoke...");
2696
+ run2("git pull", dir);
2697
+ log("Installing dependencies...");
2698
+ run2("npm ci", dir);
2699
+ log("Building...");
2700
+ run2("npm run build", dir);
2701
+ }
2702
+ function updateYoke({ dir, install, targets, run: run2 = runInherited, log, error }) {
2703
+ log(`yoke is installed at ${dir}`);
2704
+ if (install === "unknown") {
2705
+ error(
2706
+ `Could not tell how yoke was installed at ${dir}. Expected a git clone or a global npm install; reinstall with \`npm i -g ${packageName}@latest\`.`
2707
+ );
2708
+ return 1;
2709
+ }
2710
+ try {
2711
+ installLatest(install, dir, run2, log);
2712
+ } catch {
2713
+ error("Updating yoke failed, so no skills were re-synced.");
2714
+ return 1;
2715
+ }
2716
+ const found = targets();
2717
+ if (found.length === 0) {
2718
+ log(noTargetsMessage);
2719
+ return 0;
2720
+ }
2721
+ for (const target of found) {
2722
+ const command = syncCommands[target];
2723
+ log(`Running ${command}...`);
2724
+ try {
2725
+ run2(command);
2726
+ } catch {
2727
+ error(`${command} failed.`);
2728
+ return 1;
2729
+ }
2730
+ }
2731
+ return 0;
2732
+ }
2733
+
2624
2734
  // src/cli.ts
2625
2735
  var { version } = package_default;
2626
2736
  var program = new Command("yoke").description("Lean Claude Code skills and the CLI they call.").version(version);
2627
2737
  program.command("sync").description("Write yoke's skills into this repo's .claude/skills, or ~/.claude/skills with --user").option("--user", "write to ~/.claude/skills instead, for every repo").option("--force", "overwrite same-named skills that yoke didn't write").action((options) => {
2628
2738
  const user = options.user ?? false;
2629
2739
  const cwd = process.cwd();
2630
- const display = (path) => path.startsWith(cwd) ? relative(cwd, path) : path;
2740
+ const display = (path) => path.startsWith(cwd) ? relative2(cwd, path) : path;
2631
2741
  const { written, skipped } = syncSkills(skillsRoot(user), skillsRoot(true), options.force ?? false);
2632
2742
  for (const file of written) console.log(`wrote ${display(file)}`);
2633
2743
  for (const dir of skipped) console.log(`skipped ${display(dir)} (not yoke's skill; rerun with --force to overwrite it)`);
2634
2744
  });
2745
+ program.command("update").description("Update yoke to the latest version, then re-sync its skills wherever they were synced before").action(() => {
2746
+ const dir = installDir();
2747
+ process.exitCode = updateYoke({
2748
+ dir,
2749
+ install: detectInstall(dir),
2750
+ targets: () => syncTargets(),
2751
+ log: console.log,
2752
+ error: console.error
2753
+ });
2754
+ });
2635
2755
  var pr = program.command("pr").description("Pull request commands");
2636
2756
  pr.command("raise").description("Validate a PR title and body, then create the PR with gh").requiredOption("--title <title>", "single-line PR title").requiredOption("--body <body>", "PR body with ## What, ## Why and optionally ## How").option("--base <branch>", "base branch (defaults to the repo default)").option("--no-draft", "create a ready-for-review PR instead of a draft").addHelpText("after", raiseGuide).action((options) => {
2637
2757
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makerx/yoke",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Lean, user-invoked Claude Code skills for MakerX AI-assisted delivery, plus the CLI they call.",
5
5
  "license": "MIT",
6
6
  "author": "MakerX",