@sushanglewis/lincoln 1.8.0 → 1.8.3

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/dist/main.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import fs11 from "fs";
5
- import path12 from "path";
4
+ import fs12 from "fs";
5
+ import path13 from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
8
8
  // src/cli.ts
@@ -399,8 +399,8 @@ function initProject(projectRoot, options = {}) {
399
399
  }
400
400
 
401
401
  // src/commands/install.ts
402
- import fs7 from "fs";
403
- import path8 from "path";
402
+ import fs8 from "fs";
403
+ import path9 from "path";
404
404
  import { spawn as spawn3 } from "child_process";
405
405
 
406
406
  // src/lib/syncClaude.ts
@@ -438,7 +438,12 @@ var COPY_SUBDIRS = [
438
438
  "stages",
439
439
  "schemas",
440
440
  "templates",
441
- "harnesses"
441
+ "harnesses",
442
+ // Policies are data files consumed by hooks at runtime (e.g.
443
+ // .claude/policies/less-is-more.md read by on-session-start.sh,
444
+ // security.yaml read by pre-tool-use.sh). Without syncing them into the
445
+ // project, vendored installs silently skip those guardrails (#126).
446
+ "policies"
442
447
  ];
443
448
  function sha256(filePath) {
444
449
  const data = fs5.readFileSync(filePath);
@@ -659,9 +664,265 @@ async function syncHarness(harnessId, opts, syncClaude) {
659
664
  return report;
660
665
  }
661
666
 
662
- // src/lib/harnessDetect.ts
667
+ // src/lib/syncSkills.ts
663
668
  import fs6 from "fs";
664
669
  import path7 from "path";
670
+ import { execFile } from "child_process";
671
+ import yaml2 from "js-yaml";
672
+
673
+ // src/lib/semver.ts
674
+ var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
675
+ var NUMERIC_IDENTIFIER = /^\d+$/;
676
+ function compareVersions(a, b) {
677
+ const pa = parseVersion(a);
678
+ const pb = parseVersion(b);
679
+ if (!pa || !pb) {
680
+ return void 0;
681
+ }
682
+ if (pa.major !== pb.major) return pa.major - pb.major;
683
+ if (pa.minor !== pb.minor) return pa.minor - pb.minor;
684
+ if (pa.patch !== pb.patch) return pa.patch - pb.patch;
685
+ return comparePrerelease(pa.prerelease, pb.prerelease);
686
+ }
687
+ function parseVersion(version) {
688
+ const match = SEMVER_CORE.exec(version.trim());
689
+ if (!match) {
690
+ return void 0;
691
+ }
692
+ return {
693
+ major: Number(match[1]),
694
+ minor: Number(match[2]),
695
+ patch: Number(match[3]),
696
+ prerelease: match[4] ? match[4].split(".") : []
697
+ };
698
+ }
699
+ function comparePrerelease(a, b) {
700
+ if (a.length === 0 && b.length === 0) return 0;
701
+ if (a.length === 0) return 1;
702
+ if (b.length === 0) return -1;
703
+ const shared = Math.min(a.length, b.length);
704
+ for (let i = 0; i < shared; i++) {
705
+ const x = a[i];
706
+ const y = b[i];
707
+ if (x === y) continue;
708
+ const xNumeric = NUMERIC_IDENTIFIER.test(x);
709
+ const yNumeric = NUMERIC_IDENTIFIER.test(y);
710
+ if (xNumeric && yNumeric) return Number(x) - Number(y);
711
+ if (xNumeric) return -1;
712
+ if (yNumeric) return 1;
713
+ return x < y ? -1 : 1;
714
+ }
715
+ return a.length - b.length;
716
+ }
717
+
718
+ // src/lib/syncSkills.ts
719
+ var GIT_SOURCE = /^(https?:\/\/|git@)/;
720
+ var FULL_SHA = /^[0-9a-f]{40}$/i;
721
+ var PINNED_SEMVER = /^v?(\d+\.\d+\.\d+)$/;
722
+ var NPM_GLOBAL_INSTALL = /^npm install -g (\S+)$/;
723
+ var MAX_BUFFER = 16 * 1024 * 1024;
724
+ var defaultRunner = (cmd, args) => new Promise((resolvePromise, rejectPromise) => {
725
+ execFile(cmd, args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
726
+ if (err && !("code" in err && typeof err.code === "number")) {
727
+ rejectPromise(err);
728
+ return;
729
+ }
730
+ const code = err && typeof err.code === "number" ? err.code : 0;
731
+ resolvePromise({ code, stdout: String(stdout), stderr: String(stderr) });
732
+ });
733
+ });
734
+ async function syncExternalSkills(options) {
735
+ const report = { entries: [], warnings: [] };
736
+ const manifestPath = path7.join(options.payloadRoot, ".claude", "skills", "dependencies.yaml");
737
+ if (!fs6.existsSync(manifestPath)) {
738
+ report.warnings.push(`skill dependency manifest not found at ${manifestPath}; skipping external skill sync`);
739
+ return report;
740
+ }
741
+ let manifest;
742
+ try {
743
+ manifest = yaml2.load(fs6.readFileSync(manifestPath, "utf8")) ?? {};
744
+ } catch (err) {
745
+ report.warnings.push(
746
+ `failed to parse ${manifestPath}: ${err instanceof Error ? err.message : String(err)}`
747
+ );
748
+ return report;
749
+ }
750
+ const runCommand3 = options.runCommand ?? defaultRunner;
751
+ const platform = options.platform ?? (process.platform === "darwin" ? "macos" : process.platform === "linux" ? "linux" : process.platform);
752
+ for (const [name, cfg] of Object.entries(manifest.skills ?? {})) {
753
+ if (!cfg.source || cfg.source === "inline" || !GIT_SOURCE.test(cfg.source)) {
754
+ continue;
755
+ }
756
+ const enabled = cfg.default_install ?? cfg.required ?? true;
757
+ if (!enabled) {
758
+ report.entries.push({ name, status: "skipped-optional", detail: "default_install is false" });
759
+ continue;
760
+ }
761
+ if (cfg.type === "cli") {
762
+ await syncCli(name, cfg, options, platform, runCommand3, report);
763
+ } else {
764
+ await syncGitSkill(name, cfg, options, runCommand3, report);
765
+ }
766
+ }
767
+ return report;
768
+ }
769
+ async function syncGitSkill(name, cfg, options, runCommand3, report) {
770
+ const source = cfg.source;
771
+ const ref = cfg.ref ?? "";
772
+ const target = path7.join(options.skillsDir, name);
773
+ if (options.dryRun) {
774
+ const planned = fs6.existsSync(target) ? "verify pinned ref and update if behind" : `clone and pin to ${ref}`;
775
+ report.entries.push({ name, status: "dry-run", detail: `would ${planned}` });
776
+ return;
777
+ }
778
+ if (!fs6.existsSync(target)) {
779
+ const clone = await runCommand3("git", ["clone", source, target]);
780
+ if (clone.code !== 0) {
781
+ fail(report, name, `git clone failed: ${clone.stderr.trim() || `exit code ${clone.code}`}`);
782
+ return;
783
+ }
784
+ const checkout = await runCommand3("git", ["-C", target, "checkout", ref]);
785
+ if (checkout.code !== 0) {
786
+ fail(report, name, `git checkout ${ref} failed: ${checkout.stderr.trim() || `exit code ${checkout.code}`}`);
787
+ return;
788
+ }
789
+ report.entries.push({ name, status: "installed", detail: ref });
790
+ return;
791
+ }
792
+ const status = await runCommand3("git", ["-C", target, "status", "--porcelain"]);
793
+ if (status.code !== 0) {
794
+ fail(report, name, `git status failed (not a git repository?): ${status.stderr.trim() || `exit code ${status.code}`}`);
795
+ return;
796
+ }
797
+ if (status.stdout.trim() !== "") {
798
+ const detail = "working tree is dirty; skipping update (commit or stash local changes first)";
799
+ report.entries.push({ name, status: "skipped-dirty", detail });
800
+ report.warnings.push(`${name}: ${detail}`);
801
+ return;
802
+ }
803
+ const head = await runCommand3("git", ["-C", target, "rev-parse", "HEAD"]);
804
+ if (head.code !== 0) {
805
+ fail(report, name, `git rev-parse failed: ${head.stderr.trim() || `exit code ${head.code}`}`);
806
+ return;
807
+ }
808
+ if (head.stdout.trim() === ref) {
809
+ report.entries.push({ name, status: "current", detail: ref });
810
+ return;
811
+ }
812
+ const advertised = await isPinAdvertised(source, ref, runCommand3);
813
+ if (advertised) {
814
+ const fetch3 = await runCommand3("git", ["-C", target, "fetch", "origin", ref]);
815
+ if (fetch3.code === 0 && await checkoutFetchHead(name, target, runCommand3, report)) {
816
+ return;
817
+ }
818
+ if (fetch3.code !== 0) {
819
+ fail(report, name, `git fetch origin ${ref} failed: ${fetch3.stderr.trim() || `exit code ${fetch3.code}`}`);
820
+ return;
821
+ }
822
+ return;
823
+ }
824
+ const fetch2 = await runCommand3("git", ["-C", target, "fetch", "origin", ref]);
825
+ if (fetch2.code === 0) {
826
+ await checkoutFetchHead(name, target, runCommand3, report);
827
+ return;
828
+ }
829
+ if (FULL_SHA.test(ref)) {
830
+ const fetchAll = await runCommand3("git", ["-C", target, "fetch", "origin"]);
831
+ if (fetchAll.code !== 0) {
832
+ fail(report, name, `git fetch origin failed: ${fetchAll.stderr.trim() || `exit code ${fetchAll.code}`}`);
833
+ return;
834
+ }
835
+ const checkout = await runCommand3("git", ["-C", target, "checkout", ref]);
836
+ if (checkout.code !== 0) {
837
+ fail(report, name, `git checkout ${ref} failed: ${checkout.stderr.trim() || `exit code ${checkout.code}`}`);
838
+ return;
839
+ }
840
+ report.entries.push({ name, status: "updated", detail: ref });
841
+ return;
842
+ }
843
+ fail(report, name, `git fetch origin ${ref} failed: ${fetch2.stderr.trim() || `exit code ${fetch2.code}`}`);
844
+ }
845
+ async function isPinAdvertised(source, ref, runCommand3) {
846
+ const remote = await runCommand3("git", ["ls-remote", source]);
847
+ if (remote.code !== 0) {
848
+ return false;
849
+ }
850
+ return remote.stdout.split("\n").some((line) => line.split(" ")[0]?.trim() === ref);
851
+ }
852
+ async function checkoutFetchHead(name, target, runCommand3, report) {
853
+ const checkout = await runCommand3("git", ["-C", target, "checkout", "FETCH_HEAD"]);
854
+ if (checkout.code !== 0) {
855
+ fail(report, name, `git checkout FETCH_HEAD failed: ${checkout.stderr.trim() || `exit code ${checkout.code}`}`);
856
+ return false;
857
+ }
858
+ report.entries.push({ name, status: "updated", detail: "checked out FETCH_HEAD" });
859
+ return true;
860
+ }
861
+ async function syncCli(name, cfg, options, platform, runCommand3, report) {
862
+ const binary = cfg.binary ?? name;
863
+ const installCommand = cfg.platforms?.[platform] ?? cfg.platforms?.linux;
864
+ const npmPackage = installCommand ? parseNpmGlobalInstall(installCommand) : void 0;
865
+ const pinned = cfg.ref ? pinnedSemver(cfg.ref) : void 0;
866
+ if (options.dryRun) {
867
+ const managed = npmPackage && pinned ? `install ${npmPackage}@${pinned} if missing or older` : "verify presence";
868
+ report.entries.push({ name, status: "dry-run", detail: `would ${managed}` });
869
+ return;
870
+ }
871
+ const whichCmd = process.platform === "win32" ? "where" : "which";
872
+ const found = await runCommand3(whichCmd, [binary]);
873
+ if (found.code !== 0) {
874
+ if (npmPackage && pinned) {
875
+ await npmInstall(name, npmPackage, pinned, runCommand3, report, "installed");
876
+ return;
877
+ }
878
+ fail(report, name, `CLI '${binary}' not found. Install manually (${platform}): ${installCommand ?? "see dependency manifest"}`);
879
+ return;
880
+ }
881
+ if (!npmPackage || !pinned) {
882
+ report.entries.push({ name, status: "current", detail: `${binary} present` });
883
+ return;
884
+ }
885
+ const version = await runCommand3(binary, ["--version"]);
886
+ const installed = version.code === 0 ? version.stdout.split("\n")[0]?.trim() ?? "" : "";
887
+ const comparison = installed ? compareVersions(installed, pinned) : void 0;
888
+ if (comparison === void 0) {
889
+ report.entries.push({
890
+ name,
891
+ status: "current",
892
+ detail: `could not parse installed version '${installed}'; leaving ${binary} as-is`
893
+ });
894
+ return;
895
+ }
896
+ if (comparison >= 0) {
897
+ report.entries.push({ name, status: "current", detail: `${binary} ${installed}` });
898
+ return;
899
+ }
900
+ await npmInstall(name, npmPackage, pinned, runCommand3, report, "updated");
901
+ }
902
+ async function npmInstall(name, npmPackage, version, runCommand3, report, status) {
903
+ const result = await runCommand3("npm", ["install", "-g", `${npmPackage}@${version}`]);
904
+ if (result.code !== 0) {
905
+ fail(report, name, `npm install -g ${npmPackage}@${version} failed: ${result.stderr.trim() || `exit code ${result.code}`}`);
906
+ return;
907
+ }
908
+ report.entries.push({ name, status, detail: `${npmPackage}@${version}` });
909
+ }
910
+ function parseNpmGlobalInstall(command) {
911
+ const match = NPM_GLOBAL_INSTALL.exec(command.trim());
912
+ return match?.[1];
913
+ }
914
+ function pinnedSemver(ref) {
915
+ const match = PINNED_SEMVER.exec(ref.trim());
916
+ return match?.[0].replace(/^v/, "");
917
+ }
918
+ function fail(report, name, detail) {
919
+ report.entries.push({ name, status: "failed", detail });
920
+ report.warnings.push(`${name}: ${detail}`);
921
+ }
922
+
923
+ // src/lib/harnessDetect.ts
924
+ import fs7 from "fs";
925
+ import path8 from "path";
665
926
  import os2 from "os";
666
927
  var HARNESS_SPECS = [
667
928
  {
@@ -675,9 +936,9 @@ var HARNESS_SPECS = [
675
936
  function detectGlobalHarnesses(overrideHomeDir) {
676
937
  const homeDir = overrideHomeDir ?? os2.homedir();
677
938
  return HARNESS_SPECS.map((spec) => {
678
- const configDir = path7.join(homeDir, spec.configDirName);
679
- const installed = fs6.existsSync(configDir);
680
- const pluginManaged = spec.pluginMarker ? fs6.existsSync(path7.join(configDir, spec.pluginMarker)) : false;
939
+ const configDir = path8.join(homeDir, spec.configDirName);
940
+ const installed = fs7.existsSync(configDir);
941
+ const pluginManaged = spec.pluginMarker ? fs7.existsSync(path8.join(configDir, spec.pluginMarker)) : false;
681
942
  return {
682
943
  id: spec.id,
683
944
  installed,
@@ -869,6 +1130,7 @@ function createDefaultDeps2() {
869
1130
  paths: resolveLincolnPaths(),
870
1131
  payloadRoot: resolvePayloadRoot() || "",
871
1132
  syncHarnesses,
1133
+ syncExternalSkills,
872
1134
  createPrompt,
873
1135
  resolvePythonForVenv,
874
1136
  isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY)
@@ -881,7 +1143,7 @@ async function install(options, deps = createDefaultDeps2()) {
881
1143
  return 1;
882
1144
  }
883
1145
  const payloadRoot = deps.payloadRoot;
884
- if (!payloadRoot || !fs7.existsSync(payloadRoot)) {
1146
+ if (!payloadRoot || !fs8.existsSync(payloadRoot)) {
885
1147
  console.error('Lincoln payload not found. Run "npm install -g @sushanglewis/lincoln" first.');
886
1148
  return 1;
887
1149
  }
@@ -900,15 +1162,15 @@ async function install(options, deps = createDefaultDeps2()) {
900
1162
  return 1;
901
1163
  }
902
1164
  if (!options.dryRun) {
903
- const versionDir = path8.join(deps.paths.versionsDir, version);
904
- fs7.mkdirSync(versionDir, { recursive: true });
905
- fs7.cpSync(payloadRoot, versionDir, { recursive: true, force: true });
1165
+ const versionDir = path9.join(deps.paths.versionsDir, version);
1166
+ fs8.mkdirSync(versionDir, { recursive: true });
1167
+ fs8.cpSync(payloadRoot, versionDir, { recursive: true, force: true });
906
1168
  try {
907
- fs7.rmSync(deps.paths.currentDir, { recursive: true, force: true });
908
- fs7.symlinkSync(versionDir, deps.paths.currentDir);
1169
+ fs8.rmSync(deps.paths.currentDir, { recursive: true, force: true });
1170
+ fs8.symlinkSync(versionDir, deps.paths.currentDir);
909
1171
  } catch {
910
- fs7.rmSync(deps.paths.currentDir, { recursive: true, force: true });
911
- fs7.cpSync(versionDir, deps.paths.currentDir, { recursive: true, force: true });
1172
+ fs8.rmSync(deps.paths.currentDir, { recursive: true, force: true });
1173
+ fs8.cpSync(versionDir, deps.paths.currentDir, { recursive: true, force: true });
912
1174
  }
913
1175
  }
914
1176
  const reports = await deps.syncHarnesses({
@@ -928,9 +1190,30 @@ async function install(options, deps = createDefaultDeps2()) {
928
1190
  managedFiles: collectManagedFiles(reports)
929
1191
  });
930
1192
  }
931
- printSummary(options, version, harnesses, reports);
1193
+ const skillReport = await syncSkillLibraries(options, deps, harnesses);
1194
+ printSummary(options, version, harnesses, reports, skillReport);
932
1195
  return 0;
933
1196
  }
1197
+ async function syncSkillLibraries(options, deps, harnesses) {
1198
+ if (!harnesses.includes("claude-code") || !deps.syncExternalSkills) {
1199
+ return void 0;
1200
+ }
1201
+ const payloadRoot = deps.paths.currentDir;
1202
+ const manifestPath = path9.join(payloadRoot, ".claude", "skills", "dependencies.yaml");
1203
+ if (!fs8.existsSync(manifestPath)) {
1204
+ return void 0;
1205
+ }
1206
+ try {
1207
+ return await deps.syncExternalSkills({
1208
+ payloadRoot,
1209
+ skillsDir: path9.join(deps.paths.claudeDir, "skills"),
1210
+ dryRun: options.dryRun
1211
+ });
1212
+ } catch (err) {
1213
+ console.warn(` warning: external skill sync failed: ${err instanceof Error ? err.message : String(err)}`);
1214
+ return void 0;
1215
+ }
1216
+ }
934
1217
  async function resolvePythonIfNeeded(options, deps, harnesses) {
935
1218
  const needsPython = !options.dryRun && !options.noVenv && harnesses.some((id) => id === "codex" || id === "opencode");
936
1219
  if (!needsPython) {
@@ -991,7 +1274,7 @@ function collectManagedFiles(reports) {
991
1274
  }
992
1275
  return files;
993
1276
  }
994
- function printSummary(options, version, harnesses, reports) {
1277
+ function printSummary(options, version, harnesses, reports, skillReport) {
995
1278
  const warnings = collectWarnings(reports);
996
1279
  if (options.dryRun) {
997
1280
  console.log(`Dry run: would install Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
@@ -1002,6 +1285,13 @@ function printSummary(options, version, harnesses, reports) {
1002
1285
  const count = report.written.length + report.skipped.length + report.preserved.length;
1003
1286
  console.log(` ${harnessId}: ${count} file(s)`);
1004
1287
  }
1288
+ if (skillReport) {
1289
+ for (const entry of skillReport.entries) {
1290
+ const detail = entry.detail ? ` (${entry.detail})` : "";
1291
+ console.log(` skill ${entry.name}: ${entry.status}${detail}`);
1292
+ }
1293
+ warnings.push(...skillReport.warnings);
1294
+ }
1005
1295
  for (const warning of warnings) {
1006
1296
  console.warn(` warning: ${warning}`);
1007
1297
  }
@@ -1062,7 +1352,7 @@ async function resolvePythonForVenv(envOverride = process.env.LINCOLN_PYTHON, ca
1062
1352
  return void 0;
1063
1353
  }
1064
1354
  function venvPythonPath(paths) {
1065
- return path8.join(
1355
+ return path9.join(
1066
1356
  paths.venvDir,
1067
1357
  process.platform === "win32" ? "Scripts" : "bin",
1068
1358
  process.platform === "win32" ? "python.exe" : "python"
@@ -1070,17 +1360,17 @@ function venvPythonPath(paths) {
1070
1360
  }
1071
1361
  async function setupVenv(paths, payloadRoot, pythonCommand) {
1072
1362
  const pythonExe = venvPythonPath(paths);
1073
- if (!fs7.existsSync(pythonExe)) {
1363
+ if (!fs8.existsSync(pythonExe)) {
1074
1364
  console.log("Creating Lincoln virtual environment...");
1075
1365
  const venvCode = await runCommand2(pythonCommand, ["-m", "venv", paths.venvDir]);
1076
1366
  if (venvCode !== 0) {
1077
1367
  throw new Error(`Failed to create virtual environment (exit code ${venvCode})`);
1078
1368
  }
1079
1369
  }
1080
- const requirementsPath = path8.join(payloadRoot, "requirements.txt");
1081
- if (fs7.existsSync(requirementsPath)) {
1370
+ const requirementsPath = path9.join(payloadRoot, "requirements.txt");
1371
+ if (fs8.existsSync(requirementsPath)) {
1082
1372
  console.log("Installing Python dependencies...");
1083
- const pipExe = path8.join(
1373
+ const pipExe = path9.join(
1084
1374
  paths.venvDir,
1085
1375
  process.platform === "win32" ? "Scripts" : "bin",
1086
1376
  process.platform === "win32" ? "pip.exe" : "pip"
@@ -1152,51 +1442,6 @@ function encodePackageName(packageName) {
1152
1442
  return encodeURIComponent(packageName);
1153
1443
  }
1154
1444
 
1155
- // src/lib/semver.ts
1156
- var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
1157
- var NUMERIC_IDENTIFIER = /^\d+$/;
1158
- function compareVersions(a, b) {
1159
- const pa = parseVersion(a);
1160
- const pb = parseVersion(b);
1161
- if (!pa || !pb) {
1162
- return void 0;
1163
- }
1164
- if (pa.major !== pb.major) return pa.major - pb.major;
1165
- if (pa.minor !== pb.minor) return pa.minor - pb.minor;
1166
- if (pa.patch !== pb.patch) return pa.patch - pb.patch;
1167
- return comparePrerelease(pa.prerelease, pb.prerelease);
1168
- }
1169
- function parseVersion(version) {
1170
- const match = SEMVER_CORE.exec(version.trim());
1171
- if (!match) {
1172
- return void 0;
1173
- }
1174
- return {
1175
- major: Number(match[1]),
1176
- minor: Number(match[2]),
1177
- patch: Number(match[3]),
1178
- prerelease: match[4] ? match[4].split(".") : []
1179
- };
1180
- }
1181
- function comparePrerelease(a, b) {
1182
- if (a.length === 0 && b.length === 0) return 0;
1183
- if (a.length === 0) return 1;
1184
- if (b.length === 0) return -1;
1185
- const shared = Math.min(a.length, b.length);
1186
- for (let i = 0; i < shared; i++) {
1187
- const x = a[i];
1188
- const y = b[i];
1189
- if (x === y) continue;
1190
- const xNumeric = NUMERIC_IDENTIFIER.test(x);
1191
- const yNumeric = NUMERIC_IDENTIFIER.test(y);
1192
- if (xNumeric && yNumeric) return Number(x) - Number(y);
1193
- if (xNumeric) return -1;
1194
- if (yNumeric) return 1;
1195
- return x < y ? -1 : 1;
1196
- }
1197
- return a.length - b.length;
1198
- }
1199
-
1200
1445
  // src/commands/update.ts
1201
1446
  var PACKAGE_NAME = "@sushanglewis/lincoln";
1202
1447
  function createDefaultDeps3() {
@@ -1276,8 +1521,8 @@ function runNpmInstallGlobal(packageName, version) {
1276
1521
  }
1277
1522
 
1278
1523
  // src/commands/use.ts
1279
- import fs8 from "fs";
1280
- import path9 from "path";
1524
+ import fs9 from "fs";
1525
+ import path10 from "path";
1281
1526
  import { spawn as spawn5 } from "child_process";
1282
1527
  var PACKAGE_NAME2 = "@sushanglewis/lincoln";
1283
1528
  var SEMVER_REGEX = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1319,8 +1564,8 @@ async function use(spec, options, deps = {}) {
1319
1564
  console.error(`Failed to resolve version ${spec}: ${message}`);
1320
1565
  return 1;
1321
1566
  }
1322
- const versionDir = path9.join(resolved.paths.versionsDir, version);
1323
- const alreadyInstalled = fs8.existsSync(versionDir);
1567
+ const versionDir = path10.join(resolved.paths.versionsDir, version);
1568
+ const alreadyInstalled = fs9.existsSync(versionDir);
1324
1569
  if (options.dryRun) {
1325
1570
  if (alreadyInstalled) {
1326
1571
  console.log(`Dry run: would switch ${resolved.paths.currentDir} to ${versionDir}`);
@@ -1343,7 +1588,7 @@ async function use(spec, options, deps = {}) {
1343
1588
  console.error(`Failed to install ${PACKAGE_NAME2}@${version}: ${message}`);
1344
1589
  return 1;
1345
1590
  }
1346
- if (!fs8.existsSync(versionDir)) {
1591
+ if (!fs9.existsSync(versionDir)) {
1347
1592
  try {
1348
1593
  await copyVersionFromNpmGlobal(resolved, versionDir);
1349
1594
  } catch (err) {
@@ -1352,7 +1597,7 @@ async function use(spec, options, deps = {}) {
1352
1597
  return 1;
1353
1598
  }
1354
1599
  }
1355
- if (!fs8.existsSync(versionDir)) {
1600
+ if (!fs9.existsSync(versionDir)) {
1356
1601
  console.error(
1357
1602
  `Provisioning ${PACKAGE_NAME2}@${version} did not produce ${versionDir}; cannot switch versions.`
1358
1603
  );
@@ -1389,21 +1634,21 @@ async function copyVersionFromNpmGlobal(deps, versionDir) {
1389
1634
  if (!globalRoot) {
1390
1635
  return;
1391
1636
  }
1392
- const packageRoot = path9.join(globalRoot, ...PACKAGE_NAME2.split("/"));
1393
- if (!fs8.existsSync(packageRoot)) {
1637
+ const packageRoot = path10.join(globalRoot, ...PACKAGE_NAME2.split("/"));
1638
+ if (!fs9.existsSync(packageRoot)) {
1394
1639
  return;
1395
1640
  }
1396
- fs8.mkdirSync(path9.dirname(versionDir), { recursive: true });
1397
- fs8.cpSync(packageRoot, versionDir, { recursive: true });
1641
+ fs9.mkdirSync(path10.dirname(versionDir), { recursive: true });
1642
+ fs9.cpSync(packageRoot, versionDir, { recursive: true });
1398
1643
  }
1399
1644
  function switchCurrentLink(paths, versionDir) {
1400
- fs8.mkdirSync(paths.lincolnHome, { recursive: true });
1645
+ fs9.mkdirSync(paths.lincolnHome, { recursive: true });
1401
1646
  try {
1402
- fs8.rmSync(paths.currentDir, { recursive: true, force: true });
1403
- fs8.symlinkSync(versionDir, paths.currentDir);
1647
+ fs9.rmSync(paths.currentDir, { recursive: true, force: true });
1648
+ fs9.symlinkSync(versionDir, paths.currentDir);
1404
1649
  } catch {
1405
- fs8.rmSync(paths.currentDir, { recursive: true, force: true });
1406
- fs8.cpSync(versionDir, paths.currentDir, { recursive: true, force: true });
1650
+ fs9.rmSync(paths.currentDir, { recursive: true, force: true });
1651
+ fs9.cpSync(versionDir, paths.currentDir, { recursive: true, force: true });
1407
1652
  }
1408
1653
  }
1409
1654
  function defaultInstallOptions2() {
@@ -1439,12 +1684,12 @@ function runNpmRootGlobal() {
1439
1684
  }
1440
1685
 
1441
1686
  // src/commands/migrateProject.ts
1442
- import fs10 from "fs";
1443
- import path11 from "path";
1687
+ import fs11 from "fs";
1688
+ import path12 from "path";
1444
1689
 
1445
1690
  // src/lib/projectFiles.ts
1446
- import fs9 from "fs";
1447
- import path10 from "path";
1691
+ import fs10 from "fs";
1692
+ import path11 from "path";
1448
1693
  import crypto2 from "crypto";
1449
1694
  var FRAMEWORK_ALLOWLIST_DIRS = [".claude", ".claude-plugin", "scripts", "tools"];
1450
1695
  var FRAMEWORK_ALLOWLIST_FILES = [
@@ -1479,10 +1724,10 @@ var FRAMEWORK_SKIP_PATHS = /* @__PURE__ */ new Set([
1479
1724
  "tools/lincoln-installer/coverage"
1480
1725
  ]);
1481
1726
  function sha2562(filePath) {
1482
- return crypto2.createHash("sha256").update(fs9.readFileSync(filePath)).digest("hex");
1727
+ return crypto2.createHash("sha256").update(fs10.readFileSync(filePath)).digest("hex");
1483
1728
  }
1484
1729
  function isDenied(relativePath) {
1485
- const parts = relativePath.split(path10.sep);
1730
+ const parts = relativePath.split(path11.sep);
1486
1731
  for (const part of parts) {
1487
1732
  if (FRAMEWORK_DENYLIST_NAMES.has(part)) return true;
1488
1733
  for (const prefix of FRAMEWORK_DENYLIST_PREFIXES) {
@@ -1490,14 +1735,14 @@ function isDenied(relativePath) {
1490
1735
  }
1491
1736
  }
1492
1737
  for (const skip of FRAMEWORK_SKIP_PATHS) {
1493
- if (relativePath === skip || relativePath.startsWith(skip + path10.sep)) return true;
1738
+ if (relativePath === skip || relativePath.startsWith(skip + path11.sep)) return true;
1494
1739
  }
1495
1740
  return false;
1496
1741
  }
1497
1742
  function isAllowlisted(relativePath) {
1498
1743
  if (FRAMEWORK_ALLOWLIST_FILES.includes(relativePath)) return true;
1499
1744
  for (const dir of FRAMEWORK_ALLOWLIST_DIRS) {
1500
- if (relativePath === dir || relativePath.startsWith(dir + path10.sep)) return true;
1745
+ if (relativePath === dir || relativePath.startsWith(dir + path11.sep)) return true;
1501
1746
  }
1502
1747
  return false;
1503
1748
  }
@@ -1518,10 +1763,10 @@ function isPreserved(relativePath) {
1518
1763
  function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1519
1764
  const report = { removable: [], modified: [], preserved: [] };
1520
1765
  function walk(dir) {
1521
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
1766
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
1522
1767
  for (const entry of entries) {
1523
- const fullPath = path10.join(dir, entry.name);
1524
- const relativePath = path10.relative(projectRoot, fullPath);
1768
+ const fullPath = path11.join(dir, entry.name);
1769
+ const relativePath = path11.relative(projectRoot, fullPath);
1525
1770
  if (entry.isDirectory()) {
1526
1771
  if (isPreservedDir(relativePath)) {
1527
1772
  walk(fullPath);
@@ -1536,8 +1781,8 @@ function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1536
1781
  }
1537
1782
  if (isDenied(relativePath)) continue;
1538
1783
  if (!isAllowlisted(relativePath)) continue;
1539
- const payloadFile = path10.join(payloadRoot, relativePath);
1540
- if (!fs9.existsSync(payloadFile)) {
1784
+ const payloadFile = path11.join(payloadRoot, relativePath);
1785
+ if (!fs10.existsSync(payloadFile)) {
1541
1786
  report.modified.push(relativePath);
1542
1787
  continue;
1543
1788
  }
@@ -1548,7 +1793,7 @@ function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1548
1793
  }
1549
1794
  }
1550
1795
  }
1551
- if (fs9.existsSync(projectRoot)) {
1796
+ if (fs10.existsSync(projectRoot)) {
1552
1797
  walk(projectRoot);
1553
1798
  }
1554
1799
  return report;
@@ -1564,11 +1809,11 @@ function createDefaultDeps5() {
1564
1809
  }
1565
1810
  async function migrateProject(projectRoot, options = {}, deps = createDefaultDeps5()) {
1566
1811
  const payloadRoot = deps.paths.currentDir;
1567
- if (!fs10.existsSync(payloadRoot)) {
1812
+ if (!fs11.existsSync(payloadRoot)) {
1568
1813
  console.error(`Lincoln payload not found at ${payloadRoot}. Run "lincoln install" first.`);
1569
1814
  return 1;
1570
1815
  }
1571
- const absoluteProjectRoot = path11.resolve(projectRoot);
1816
+ const absoluteProjectRoot = path12.resolve(projectRoot);
1572
1817
  const report = deps.listVendoredFrameworkFiles(payloadRoot, absoluteProjectRoot);
1573
1818
  if (options.dryRun) {
1574
1819
  console.log(`Dry run: would migrate ${absoluteProjectRoot}`);
@@ -1592,15 +1837,15 @@ async function migrateProject(projectRoot, options = {}, deps = createDefaultDep
1592
1837
  return 1;
1593
1838
  }
1594
1839
  for (const file of report.removable) {
1595
- const filePath = path11.join(absoluteProjectRoot, file);
1840
+ const filePath = path12.join(absoluteProjectRoot, file);
1596
1841
  try {
1597
- fs10.rmSync(filePath);
1598
- let dir = path11.dirname(filePath);
1599
- while (dir !== absoluteProjectRoot && fs10.existsSync(dir)) {
1600
- const entries = fs10.readdirSync(dir);
1842
+ fs11.rmSync(filePath);
1843
+ let dir = path12.dirname(filePath);
1844
+ while (dir !== absoluteProjectRoot && fs11.existsSync(dir)) {
1845
+ const entries = fs11.readdirSync(dir);
1601
1846
  if (entries.length === 0) {
1602
- fs10.rmdirSync(dir);
1603
- dir = path11.dirname(dir);
1847
+ fs11.rmdirSync(dir);
1848
+ dir = path12.dirname(dir);
1604
1849
  } else {
1605
1850
  break;
1606
1851
  }
@@ -1749,7 +1994,7 @@ async function main(argv) {
1749
1994
  console.log(USAGE);
1750
1995
  return 0;
1751
1996
  }
1752
- if (process.argv[1] && path12.resolve(fs11.realpathSync(fileURLToPath2(import.meta.url))) === path12.resolve(fs11.realpathSync(process.argv[1]))) {
1997
+ if (process.argv[1] && path13.resolve(fs12.realpathSync(fileURLToPath2(import.meta.url))) === path13.resolve(fs12.realpathSync(process.argv[1]))) {
1753
1998
  main(process.argv).then((code) => process.exit(code)).catch((err) => {
1754
1999
  console.error(err instanceof Error ? err.message : String(err));
1755
2000
  process.exit(1);