@sushanglewis/lincoln 1.7.0 → 1.8.2

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 (28) hide show
  1. package/dist/main.js +347 -107
  2. package/dist/main.js.map +1 -1
  3. package/dist/postinstall.js +306 -21
  4. package/dist/postinstall.js.map +1 -1
  5. package/framework/.claude/skills/build-product-prototype/prompts/main.md +6 -1
  6. package/framework/.claude/skills/clarify-requirements/prompts/main.md +6 -0
  7. package/framework/.claude/skills/dependencies.yaml +8 -8
  8. package/framework/.claude/skills/draft-product-design/SKILL.md +1 -0
  9. package/framework/.claude/skills/draft-product-design/prompts/main.md +26 -3
  10. package/framework/.claude/skills/lc-workflow/validators/validate.py +1 -0
  11. package/framework/.claude/stages/product-design-docs.yaml +2 -0
  12. package/framework/.claude/workflows/bug-fix.yaml +1 -0
  13. package/framework/.claude/workflows/design-spike.yaml +1 -0
  14. package/framework/.claude/workflows/existing-project-iteration.yaml +1 -0
  15. package/framework/.claude/workflows/interview-to-knowledge.yaml +1 -0
  16. package/framework/.claude/workflows/oss-first-design.yaml +1 -0
  17. package/framework/.claude-plugin/marketplace.json +2 -2
  18. package/framework/.claude-plugin/plugin.json +1 -1
  19. package/framework/.version-bump.json +1 -1
  20. package/framework/README.en.md +21 -3
  21. package/framework/README.md +23 -3
  22. package/framework/RELEASE.md +41 -1
  23. package/framework/manifest.json +24 -24
  24. package/framework/scripts/lincoln_index.py +8 -1
  25. package/framework/tools/lincoln/package-lock.json +2 -2
  26. package/framework/tools/lincoln/package.json +1 -1
  27. package/framework/tools/lincoln-installer/package.json +1 -1
  28. package/package.json +1 -1
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
@@ -659,9 +659,265 @@ async function syncHarness(harnessId, opts, syncClaude) {
659
659
  return report;
660
660
  }
661
661
 
662
- // src/lib/harnessDetect.ts
662
+ // src/lib/syncSkills.ts
663
663
  import fs6 from "fs";
664
664
  import path7 from "path";
665
+ import { execFile } from "child_process";
666
+ import yaml2 from "js-yaml";
667
+
668
+ // src/lib/semver.ts
669
+ var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
670
+ var NUMERIC_IDENTIFIER = /^\d+$/;
671
+ function compareVersions(a, b) {
672
+ const pa = parseVersion(a);
673
+ const pb = parseVersion(b);
674
+ if (!pa || !pb) {
675
+ return void 0;
676
+ }
677
+ if (pa.major !== pb.major) return pa.major - pb.major;
678
+ if (pa.minor !== pb.minor) return pa.minor - pb.minor;
679
+ if (pa.patch !== pb.patch) return pa.patch - pb.patch;
680
+ return comparePrerelease(pa.prerelease, pb.prerelease);
681
+ }
682
+ function parseVersion(version) {
683
+ const match = SEMVER_CORE.exec(version.trim());
684
+ if (!match) {
685
+ return void 0;
686
+ }
687
+ return {
688
+ major: Number(match[1]),
689
+ minor: Number(match[2]),
690
+ patch: Number(match[3]),
691
+ prerelease: match[4] ? match[4].split(".") : []
692
+ };
693
+ }
694
+ function comparePrerelease(a, b) {
695
+ if (a.length === 0 && b.length === 0) return 0;
696
+ if (a.length === 0) return 1;
697
+ if (b.length === 0) return -1;
698
+ const shared = Math.min(a.length, b.length);
699
+ for (let i = 0; i < shared; i++) {
700
+ const x = a[i];
701
+ const y = b[i];
702
+ if (x === y) continue;
703
+ const xNumeric = NUMERIC_IDENTIFIER.test(x);
704
+ const yNumeric = NUMERIC_IDENTIFIER.test(y);
705
+ if (xNumeric && yNumeric) return Number(x) - Number(y);
706
+ if (xNumeric) return -1;
707
+ if (yNumeric) return 1;
708
+ return x < y ? -1 : 1;
709
+ }
710
+ return a.length - b.length;
711
+ }
712
+
713
+ // src/lib/syncSkills.ts
714
+ var GIT_SOURCE = /^(https?:\/\/|git@)/;
715
+ var FULL_SHA = /^[0-9a-f]{40}$/i;
716
+ var PINNED_SEMVER = /^v?(\d+\.\d+\.\d+)$/;
717
+ var NPM_GLOBAL_INSTALL = /^npm install -g (\S+)$/;
718
+ var MAX_BUFFER = 16 * 1024 * 1024;
719
+ var defaultRunner = (cmd, args) => new Promise((resolvePromise, rejectPromise) => {
720
+ execFile(cmd, args, { maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
721
+ if (err && !("code" in err && typeof err.code === "number")) {
722
+ rejectPromise(err);
723
+ return;
724
+ }
725
+ const code = err && typeof err.code === "number" ? err.code : 0;
726
+ resolvePromise({ code, stdout: String(stdout), stderr: String(stderr) });
727
+ });
728
+ });
729
+ async function syncExternalSkills(options) {
730
+ const report = { entries: [], warnings: [] };
731
+ const manifestPath = path7.join(options.payloadRoot, ".claude", "skills", "dependencies.yaml");
732
+ if (!fs6.existsSync(manifestPath)) {
733
+ report.warnings.push(`skill dependency manifest not found at ${manifestPath}; skipping external skill sync`);
734
+ return report;
735
+ }
736
+ let manifest;
737
+ try {
738
+ manifest = yaml2.load(fs6.readFileSync(manifestPath, "utf8")) ?? {};
739
+ } catch (err) {
740
+ report.warnings.push(
741
+ `failed to parse ${manifestPath}: ${err instanceof Error ? err.message : String(err)}`
742
+ );
743
+ return report;
744
+ }
745
+ const runCommand3 = options.runCommand ?? defaultRunner;
746
+ const platform = options.platform ?? (process.platform === "darwin" ? "macos" : process.platform === "linux" ? "linux" : process.platform);
747
+ for (const [name, cfg] of Object.entries(manifest.skills ?? {})) {
748
+ if (!cfg.source || cfg.source === "inline" || !GIT_SOURCE.test(cfg.source)) {
749
+ continue;
750
+ }
751
+ const enabled = cfg.default_install ?? cfg.required ?? true;
752
+ if (!enabled) {
753
+ report.entries.push({ name, status: "skipped-optional", detail: "default_install is false" });
754
+ continue;
755
+ }
756
+ if (cfg.type === "cli") {
757
+ await syncCli(name, cfg, options, platform, runCommand3, report);
758
+ } else {
759
+ await syncGitSkill(name, cfg, options, runCommand3, report);
760
+ }
761
+ }
762
+ return report;
763
+ }
764
+ async function syncGitSkill(name, cfg, options, runCommand3, report) {
765
+ const source = cfg.source;
766
+ const ref = cfg.ref ?? "";
767
+ const target = path7.join(options.skillsDir, name);
768
+ if (options.dryRun) {
769
+ const planned = fs6.existsSync(target) ? "verify pinned ref and update if behind" : `clone and pin to ${ref}`;
770
+ report.entries.push({ name, status: "dry-run", detail: `would ${planned}` });
771
+ return;
772
+ }
773
+ if (!fs6.existsSync(target)) {
774
+ const clone = await runCommand3("git", ["clone", source, target]);
775
+ if (clone.code !== 0) {
776
+ fail(report, name, `git clone failed: ${clone.stderr.trim() || `exit code ${clone.code}`}`);
777
+ return;
778
+ }
779
+ const checkout = await runCommand3("git", ["-C", target, "checkout", ref]);
780
+ if (checkout.code !== 0) {
781
+ fail(report, name, `git checkout ${ref} failed: ${checkout.stderr.trim() || `exit code ${checkout.code}`}`);
782
+ return;
783
+ }
784
+ report.entries.push({ name, status: "installed", detail: ref });
785
+ return;
786
+ }
787
+ const status = await runCommand3("git", ["-C", target, "status", "--porcelain"]);
788
+ if (status.code !== 0) {
789
+ fail(report, name, `git status failed (not a git repository?): ${status.stderr.trim() || `exit code ${status.code}`}`);
790
+ return;
791
+ }
792
+ if (status.stdout.trim() !== "") {
793
+ const detail = "working tree is dirty; skipping update (commit or stash local changes first)";
794
+ report.entries.push({ name, status: "skipped-dirty", detail });
795
+ report.warnings.push(`${name}: ${detail}`);
796
+ return;
797
+ }
798
+ const head = await runCommand3("git", ["-C", target, "rev-parse", "HEAD"]);
799
+ if (head.code !== 0) {
800
+ fail(report, name, `git rev-parse failed: ${head.stderr.trim() || `exit code ${head.code}`}`);
801
+ return;
802
+ }
803
+ if (head.stdout.trim() === ref) {
804
+ report.entries.push({ name, status: "current", detail: ref });
805
+ return;
806
+ }
807
+ const advertised = await isPinAdvertised(source, ref, runCommand3);
808
+ if (advertised) {
809
+ const fetch3 = await runCommand3("git", ["-C", target, "fetch", "origin", ref]);
810
+ if (fetch3.code === 0 && await checkoutFetchHead(name, target, runCommand3, report)) {
811
+ return;
812
+ }
813
+ if (fetch3.code !== 0) {
814
+ fail(report, name, `git fetch origin ${ref} failed: ${fetch3.stderr.trim() || `exit code ${fetch3.code}`}`);
815
+ return;
816
+ }
817
+ return;
818
+ }
819
+ const fetch2 = await runCommand3("git", ["-C", target, "fetch", "origin", ref]);
820
+ if (fetch2.code === 0) {
821
+ await checkoutFetchHead(name, target, runCommand3, report);
822
+ return;
823
+ }
824
+ if (FULL_SHA.test(ref)) {
825
+ const fetchAll = await runCommand3("git", ["-C", target, "fetch", "origin"]);
826
+ if (fetchAll.code !== 0) {
827
+ fail(report, name, `git fetch origin failed: ${fetchAll.stderr.trim() || `exit code ${fetchAll.code}`}`);
828
+ return;
829
+ }
830
+ const checkout = await runCommand3("git", ["-C", target, "checkout", ref]);
831
+ if (checkout.code !== 0) {
832
+ fail(report, name, `git checkout ${ref} failed: ${checkout.stderr.trim() || `exit code ${checkout.code}`}`);
833
+ return;
834
+ }
835
+ report.entries.push({ name, status: "updated", detail: ref });
836
+ return;
837
+ }
838
+ fail(report, name, `git fetch origin ${ref} failed: ${fetch2.stderr.trim() || `exit code ${fetch2.code}`}`);
839
+ }
840
+ async function isPinAdvertised(source, ref, runCommand3) {
841
+ const remote = await runCommand3("git", ["ls-remote", source]);
842
+ if (remote.code !== 0) {
843
+ return false;
844
+ }
845
+ return remote.stdout.split("\n").some((line) => line.split(" ")[0]?.trim() === ref);
846
+ }
847
+ async function checkoutFetchHead(name, target, runCommand3, report) {
848
+ const checkout = await runCommand3("git", ["-C", target, "checkout", "FETCH_HEAD"]);
849
+ if (checkout.code !== 0) {
850
+ fail(report, name, `git checkout FETCH_HEAD failed: ${checkout.stderr.trim() || `exit code ${checkout.code}`}`);
851
+ return false;
852
+ }
853
+ report.entries.push({ name, status: "updated", detail: "checked out FETCH_HEAD" });
854
+ return true;
855
+ }
856
+ async function syncCli(name, cfg, options, platform, runCommand3, report) {
857
+ const binary = cfg.binary ?? name;
858
+ const installCommand = cfg.platforms?.[platform] ?? cfg.platforms?.linux;
859
+ const npmPackage = installCommand ? parseNpmGlobalInstall(installCommand) : void 0;
860
+ const pinned = cfg.ref ? pinnedSemver(cfg.ref) : void 0;
861
+ if (options.dryRun) {
862
+ const managed = npmPackage && pinned ? `install ${npmPackage}@${pinned} if missing or older` : "verify presence";
863
+ report.entries.push({ name, status: "dry-run", detail: `would ${managed}` });
864
+ return;
865
+ }
866
+ const whichCmd = process.platform === "win32" ? "where" : "which";
867
+ const found = await runCommand3(whichCmd, [binary]);
868
+ if (found.code !== 0) {
869
+ if (npmPackage && pinned) {
870
+ await npmInstall(name, npmPackage, pinned, runCommand3, report, "installed");
871
+ return;
872
+ }
873
+ fail(report, name, `CLI '${binary}' not found. Install manually (${platform}): ${installCommand ?? "see dependency manifest"}`);
874
+ return;
875
+ }
876
+ if (!npmPackage || !pinned) {
877
+ report.entries.push({ name, status: "current", detail: `${binary} present` });
878
+ return;
879
+ }
880
+ const version = await runCommand3(binary, ["--version"]);
881
+ const installed = version.code === 0 ? version.stdout.split("\n")[0]?.trim() ?? "" : "";
882
+ const comparison = installed ? compareVersions(installed, pinned) : void 0;
883
+ if (comparison === void 0) {
884
+ report.entries.push({
885
+ name,
886
+ status: "current",
887
+ detail: `could not parse installed version '${installed}'; leaving ${binary} as-is`
888
+ });
889
+ return;
890
+ }
891
+ if (comparison >= 0) {
892
+ report.entries.push({ name, status: "current", detail: `${binary} ${installed}` });
893
+ return;
894
+ }
895
+ await npmInstall(name, npmPackage, pinned, runCommand3, report, "updated");
896
+ }
897
+ async function npmInstall(name, npmPackage, version, runCommand3, report, status) {
898
+ const result = await runCommand3("npm", ["install", "-g", `${npmPackage}@${version}`]);
899
+ if (result.code !== 0) {
900
+ fail(report, name, `npm install -g ${npmPackage}@${version} failed: ${result.stderr.trim() || `exit code ${result.code}`}`);
901
+ return;
902
+ }
903
+ report.entries.push({ name, status, detail: `${npmPackage}@${version}` });
904
+ }
905
+ function parseNpmGlobalInstall(command) {
906
+ const match = NPM_GLOBAL_INSTALL.exec(command.trim());
907
+ return match?.[1];
908
+ }
909
+ function pinnedSemver(ref) {
910
+ const match = PINNED_SEMVER.exec(ref.trim());
911
+ return match?.[0].replace(/^v/, "");
912
+ }
913
+ function fail(report, name, detail) {
914
+ report.entries.push({ name, status: "failed", detail });
915
+ report.warnings.push(`${name}: ${detail}`);
916
+ }
917
+
918
+ // src/lib/harnessDetect.ts
919
+ import fs7 from "fs";
920
+ import path8 from "path";
665
921
  import os2 from "os";
666
922
  var HARNESS_SPECS = [
667
923
  {
@@ -675,9 +931,9 @@ var HARNESS_SPECS = [
675
931
  function detectGlobalHarnesses(overrideHomeDir) {
676
932
  const homeDir = overrideHomeDir ?? os2.homedir();
677
933
  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;
934
+ const configDir = path8.join(homeDir, spec.configDirName);
935
+ const installed = fs7.existsSync(configDir);
936
+ const pluginManaged = spec.pluginMarker ? fs7.existsSync(path8.join(configDir, spec.pluginMarker)) : false;
681
937
  return {
682
938
  id: spec.id,
683
939
  installed,
@@ -869,6 +1125,7 @@ function createDefaultDeps2() {
869
1125
  paths: resolveLincolnPaths(),
870
1126
  payloadRoot: resolvePayloadRoot() || "",
871
1127
  syncHarnesses,
1128
+ syncExternalSkills,
872
1129
  createPrompt,
873
1130
  resolvePythonForVenv,
874
1131
  isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY)
@@ -881,7 +1138,7 @@ async function install(options, deps = createDefaultDeps2()) {
881
1138
  return 1;
882
1139
  }
883
1140
  const payloadRoot = deps.payloadRoot;
884
- if (!payloadRoot || !fs7.existsSync(payloadRoot)) {
1141
+ if (!payloadRoot || !fs8.existsSync(payloadRoot)) {
885
1142
  console.error('Lincoln payload not found. Run "npm install -g @sushanglewis/lincoln" first.');
886
1143
  return 1;
887
1144
  }
@@ -900,15 +1157,15 @@ async function install(options, deps = createDefaultDeps2()) {
900
1157
  return 1;
901
1158
  }
902
1159
  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 });
1160
+ const versionDir = path9.join(deps.paths.versionsDir, version);
1161
+ fs8.mkdirSync(versionDir, { recursive: true });
1162
+ fs8.cpSync(payloadRoot, versionDir, { recursive: true, force: true });
906
1163
  try {
907
- fs7.rmSync(deps.paths.currentDir, { recursive: true, force: true });
908
- fs7.symlinkSync(versionDir, deps.paths.currentDir);
1164
+ fs8.rmSync(deps.paths.currentDir, { recursive: true, force: true });
1165
+ fs8.symlinkSync(versionDir, deps.paths.currentDir);
909
1166
  } catch {
910
- fs7.rmSync(deps.paths.currentDir, { recursive: true, force: true });
911
- fs7.cpSync(versionDir, deps.paths.currentDir, { recursive: true, force: true });
1167
+ fs8.rmSync(deps.paths.currentDir, { recursive: true, force: true });
1168
+ fs8.cpSync(versionDir, deps.paths.currentDir, { recursive: true, force: true });
912
1169
  }
913
1170
  }
914
1171
  const reports = await deps.syncHarnesses({
@@ -928,9 +1185,30 @@ async function install(options, deps = createDefaultDeps2()) {
928
1185
  managedFiles: collectManagedFiles(reports)
929
1186
  });
930
1187
  }
931
- printSummary(options, version, harnesses, reports);
1188
+ const skillReport = await syncSkillLibraries(options, deps, harnesses);
1189
+ printSummary(options, version, harnesses, reports, skillReport);
932
1190
  return 0;
933
1191
  }
1192
+ async function syncSkillLibraries(options, deps, harnesses) {
1193
+ if (!harnesses.includes("claude-code") || !deps.syncExternalSkills) {
1194
+ return void 0;
1195
+ }
1196
+ const payloadRoot = deps.paths.currentDir;
1197
+ const manifestPath = path9.join(payloadRoot, ".claude", "skills", "dependencies.yaml");
1198
+ if (!fs8.existsSync(manifestPath)) {
1199
+ return void 0;
1200
+ }
1201
+ try {
1202
+ return await deps.syncExternalSkills({
1203
+ payloadRoot,
1204
+ skillsDir: path9.join(deps.paths.claudeDir, "skills"),
1205
+ dryRun: options.dryRun
1206
+ });
1207
+ } catch (err) {
1208
+ console.warn(` warning: external skill sync failed: ${err instanceof Error ? err.message : String(err)}`);
1209
+ return void 0;
1210
+ }
1211
+ }
934
1212
  async function resolvePythonIfNeeded(options, deps, harnesses) {
935
1213
  const needsPython = !options.dryRun && !options.noVenv && harnesses.some((id) => id === "codex" || id === "opencode");
936
1214
  if (!needsPython) {
@@ -991,7 +1269,7 @@ function collectManagedFiles(reports) {
991
1269
  }
992
1270
  return files;
993
1271
  }
994
- function printSummary(options, version, harnesses, reports) {
1272
+ function printSummary(options, version, harnesses, reports, skillReport) {
995
1273
  const warnings = collectWarnings(reports);
996
1274
  if (options.dryRun) {
997
1275
  console.log(`Dry run: would install Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
@@ -1002,6 +1280,13 @@ function printSummary(options, version, harnesses, reports) {
1002
1280
  const count = report.written.length + report.skipped.length + report.preserved.length;
1003
1281
  console.log(` ${harnessId}: ${count} file(s)`);
1004
1282
  }
1283
+ if (skillReport) {
1284
+ for (const entry of skillReport.entries) {
1285
+ const detail = entry.detail ? ` (${entry.detail})` : "";
1286
+ console.log(` skill ${entry.name}: ${entry.status}${detail}`);
1287
+ }
1288
+ warnings.push(...skillReport.warnings);
1289
+ }
1005
1290
  for (const warning of warnings) {
1006
1291
  console.warn(` warning: ${warning}`);
1007
1292
  }
@@ -1062,7 +1347,7 @@ async function resolvePythonForVenv(envOverride = process.env.LINCOLN_PYTHON, ca
1062
1347
  return void 0;
1063
1348
  }
1064
1349
  function venvPythonPath(paths) {
1065
- return path8.join(
1350
+ return path9.join(
1066
1351
  paths.venvDir,
1067
1352
  process.platform === "win32" ? "Scripts" : "bin",
1068
1353
  process.platform === "win32" ? "python.exe" : "python"
@@ -1070,17 +1355,17 @@ function venvPythonPath(paths) {
1070
1355
  }
1071
1356
  async function setupVenv(paths, payloadRoot, pythonCommand) {
1072
1357
  const pythonExe = venvPythonPath(paths);
1073
- if (!fs7.existsSync(pythonExe)) {
1358
+ if (!fs8.existsSync(pythonExe)) {
1074
1359
  console.log("Creating Lincoln virtual environment...");
1075
1360
  const venvCode = await runCommand2(pythonCommand, ["-m", "venv", paths.venvDir]);
1076
1361
  if (venvCode !== 0) {
1077
1362
  throw new Error(`Failed to create virtual environment (exit code ${venvCode})`);
1078
1363
  }
1079
1364
  }
1080
- const requirementsPath = path8.join(payloadRoot, "requirements.txt");
1081
- if (fs7.existsSync(requirementsPath)) {
1365
+ const requirementsPath = path9.join(payloadRoot, "requirements.txt");
1366
+ if (fs8.existsSync(requirementsPath)) {
1082
1367
  console.log("Installing Python dependencies...");
1083
- const pipExe = path8.join(
1368
+ const pipExe = path9.join(
1084
1369
  paths.venvDir,
1085
1370
  process.platform === "win32" ? "Scripts" : "bin",
1086
1371
  process.platform === "win32" ? "pip.exe" : "pip"
@@ -1152,51 +1437,6 @@ function encodePackageName(packageName) {
1152
1437
  return encodeURIComponent(packageName);
1153
1438
  }
1154
1439
 
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
1440
  // src/commands/update.ts
1201
1441
  var PACKAGE_NAME = "@sushanglewis/lincoln";
1202
1442
  function createDefaultDeps3() {
@@ -1276,8 +1516,8 @@ function runNpmInstallGlobal(packageName, version) {
1276
1516
  }
1277
1517
 
1278
1518
  // src/commands/use.ts
1279
- import fs8 from "fs";
1280
- import path9 from "path";
1519
+ import fs9 from "fs";
1520
+ import path10 from "path";
1281
1521
  import { spawn as spawn5 } from "child_process";
1282
1522
  var PACKAGE_NAME2 = "@sushanglewis/lincoln";
1283
1523
  var SEMVER_REGEX = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1319,8 +1559,8 @@ async function use(spec, options, deps = {}) {
1319
1559
  console.error(`Failed to resolve version ${spec}: ${message}`);
1320
1560
  return 1;
1321
1561
  }
1322
- const versionDir = path9.join(resolved.paths.versionsDir, version);
1323
- const alreadyInstalled = fs8.existsSync(versionDir);
1562
+ const versionDir = path10.join(resolved.paths.versionsDir, version);
1563
+ const alreadyInstalled = fs9.existsSync(versionDir);
1324
1564
  if (options.dryRun) {
1325
1565
  if (alreadyInstalled) {
1326
1566
  console.log(`Dry run: would switch ${resolved.paths.currentDir} to ${versionDir}`);
@@ -1343,7 +1583,7 @@ async function use(spec, options, deps = {}) {
1343
1583
  console.error(`Failed to install ${PACKAGE_NAME2}@${version}: ${message}`);
1344
1584
  return 1;
1345
1585
  }
1346
- if (!fs8.existsSync(versionDir)) {
1586
+ if (!fs9.existsSync(versionDir)) {
1347
1587
  try {
1348
1588
  await copyVersionFromNpmGlobal(resolved, versionDir);
1349
1589
  } catch (err) {
@@ -1352,7 +1592,7 @@ async function use(spec, options, deps = {}) {
1352
1592
  return 1;
1353
1593
  }
1354
1594
  }
1355
- if (!fs8.existsSync(versionDir)) {
1595
+ if (!fs9.existsSync(versionDir)) {
1356
1596
  console.error(
1357
1597
  `Provisioning ${PACKAGE_NAME2}@${version} did not produce ${versionDir}; cannot switch versions.`
1358
1598
  );
@@ -1389,21 +1629,21 @@ async function copyVersionFromNpmGlobal(deps, versionDir) {
1389
1629
  if (!globalRoot) {
1390
1630
  return;
1391
1631
  }
1392
- const packageRoot = path9.join(globalRoot, ...PACKAGE_NAME2.split("/"));
1393
- if (!fs8.existsSync(packageRoot)) {
1632
+ const packageRoot = path10.join(globalRoot, ...PACKAGE_NAME2.split("/"));
1633
+ if (!fs9.existsSync(packageRoot)) {
1394
1634
  return;
1395
1635
  }
1396
- fs8.mkdirSync(path9.dirname(versionDir), { recursive: true });
1397
- fs8.cpSync(packageRoot, versionDir, { recursive: true });
1636
+ fs9.mkdirSync(path10.dirname(versionDir), { recursive: true });
1637
+ fs9.cpSync(packageRoot, versionDir, { recursive: true });
1398
1638
  }
1399
1639
  function switchCurrentLink(paths, versionDir) {
1400
- fs8.mkdirSync(paths.lincolnHome, { recursive: true });
1640
+ fs9.mkdirSync(paths.lincolnHome, { recursive: true });
1401
1641
  try {
1402
- fs8.rmSync(paths.currentDir, { recursive: true, force: true });
1403
- fs8.symlinkSync(versionDir, paths.currentDir);
1642
+ fs9.rmSync(paths.currentDir, { recursive: true, force: true });
1643
+ fs9.symlinkSync(versionDir, paths.currentDir);
1404
1644
  } catch {
1405
- fs8.rmSync(paths.currentDir, { recursive: true, force: true });
1406
- fs8.cpSync(versionDir, paths.currentDir, { recursive: true, force: true });
1645
+ fs9.rmSync(paths.currentDir, { recursive: true, force: true });
1646
+ fs9.cpSync(versionDir, paths.currentDir, { recursive: true, force: true });
1407
1647
  }
1408
1648
  }
1409
1649
  function defaultInstallOptions2() {
@@ -1439,12 +1679,12 @@ function runNpmRootGlobal() {
1439
1679
  }
1440
1680
 
1441
1681
  // src/commands/migrateProject.ts
1442
- import fs10 from "fs";
1443
- import path11 from "path";
1682
+ import fs11 from "fs";
1683
+ import path12 from "path";
1444
1684
 
1445
1685
  // src/lib/projectFiles.ts
1446
- import fs9 from "fs";
1447
- import path10 from "path";
1686
+ import fs10 from "fs";
1687
+ import path11 from "path";
1448
1688
  import crypto2 from "crypto";
1449
1689
  var FRAMEWORK_ALLOWLIST_DIRS = [".claude", ".claude-plugin", "scripts", "tools"];
1450
1690
  var FRAMEWORK_ALLOWLIST_FILES = [
@@ -1479,10 +1719,10 @@ var FRAMEWORK_SKIP_PATHS = /* @__PURE__ */ new Set([
1479
1719
  "tools/lincoln-installer/coverage"
1480
1720
  ]);
1481
1721
  function sha2562(filePath) {
1482
- return crypto2.createHash("sha256").update(fs9.readFileSync(filePath)).digest("hex");
1722
+ return crypto2.createHash("sha256").update(fs10.readFileSync(filePath)).digest("hex");
1483
1723
  }
1484
1724
  function isDenied(relativePath) {
1485
- const parts = relativePath.split(path10.sep);
1725
+ const parts = relativePath.split(path11.sep);
1486
1726
  for (const part of parts) {
1487
1727
  if (FRAMEWORK_DENYLIST_NAMES.has(part)) return true;
1488
1728
  for (const prefix of FRAMEWORK_DENYLIST_PREFIXES) {
@@ -1490,14 +1730,14 @@ function isDenied(relativePath) {
1490
1730
  }
1491
1731
  }
1492
1732
  for (const skip of FRAMEWORK_SKIP_PATHS) {
1493
- if (relativePath === skip || relativePath.startsWith(skip + path10.sep)) return true;
1733
+ if (relativePath === skip || relativePath.startsWith(skip + path11.sep)) return true;
1494
1734
  }
1495
1735
  return false;
1496
1736
  }
1497
1737
  function isAllowlisted(relativePath) {
1498
1738
  if (FRAMEWORK_ALLOWLIST_FILES.includes(relativePath)) return true;
1499
1739
  for (const dir of FRAMEWORK_ALLOWLIST_DIRS) {
1500
- if (relativePath === dir || relativePath.startsWith(dir + path10.sep)) return true;
1740
+ if (relativePath === dir || relativePath.startsWith(dir + path11.sep)) return true;
1501
1741
  }
1502
1742
  return false;
1503
1743
  }
@@ -1518,10 +1758,10 @@ function isPreserved(relativePath) {
1518
1758
  function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1519
1759
  const report = { removable: [], modified: [], preserved: [] };
1520
1760
  function walk(dir) {
1521
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
1761
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
1522
1762
  for (const entry of entries) {
1523
- const fullPath = path10.join(dir, entry.name);
1524
- const relativePath = path10.relative(projectRoot, fullPath);
1763
+ const fullPath = path11.join(dir, entry.name);
1764
+ const relativePath = path11.relative(projectRoot, fullPath);
1525
1765
  if (entry.isDirectory()) {
1526
1766
  if (isPreservedDir(relativePath)) {
1527
1767
  walk(fullPath);
@@ -1536,8 +1776,8 @@ function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1536
1776
  }
1537
1777
  if (isDenied(relativePath)) continue;
1538
1778
  if (!isAllowlisted(relativePath)) continue;
1539
- const payloadFile = path10.join(payloadRoot, relativePath);
1540
- if (!fs9.existsSync(payloadFile)) {
1779
+ const payloadFile = path11.join(payloadRoot, relativePath);
1780
+ if (!fs10.existsSync(payloadFile)) {
1541
1781
  report.modified.push(relativePath);
1542
1782
  continue;
1543
1783
  }
@@ -1548,7 +1788,7 @@ function listVendoredFrameworkFiles(payloadRoot, projectRoot) {
1548
1788
  }
1549
1789
  }
1550
1790
  }
1551
- if (fs9.existsSync(projectRoot)) {
1791
+ if (fs10.existsSync(projectRoot)) {
1552
1792
  walk(projectRoot);
1553
1793
  }
1554
1794
  return report;
@@ -1564,11 +1804,11 @@ function createDefaultDeps5() {
1564
1804
  }
1565
1805
  async function migrateProject(projectRoot, options = {}, deps = createDefaultDeps5()) {
1566
1806
  const payloadRoot = deps.paths.currentDir;
1567
- if (!fs10.existsSync(payloadRoot)) {
1807
+ if (!fs11.existsSync(payloadRoot)) {
1568
1808
  console.error(`Lincoln payload not found at ${payloadRoot}. Run "lincoln install" first.`);
1569
1809
  return 1;
1570
1810
  }
1571
- const absoluteProjectRoot = path11.resolve(projectRoot);
1811
+ const absoluteProjectRoot = path12.resolve(projectRoot);
1572
1812
  const report = deps.listVendoredFrameworkFiles(payloadRoot, absoluteProjectRoot);
1573
1813
  if (options.dryRun) {
1574
1814
  console.log(`Dry run: would migrate ${absoluteProjectRoot}`);
@@ -1592,15 +1832,15 @@ async function migrateProject(projectRoot, options = {}, deps = createDefaultDep
1592
1832
  return 1;
1593
1833
  }
1594
1834
  for (const file of report.removable) {
1595
- const filePath = path11.join(absoluteProjectRoot, file);
1835
+ const filePath = path12.join(absoluteProjectRoot, file);
1596
1836
  try {
1597
- fs10.rmSync(filePath);
1598
- let dir = path11.dirname(filePath);
1599
- while (dir !== absoluteProjectRoot && fs10.existsSync(dir)) {
1600
- const entries = fs10.readdirSync(dir);
1837
+ fs11.rmSync(filePath);
1838
+ let dir = path12.dirname(filePath);
1839
+ while (dir !== absoluteProjectRoot && fs11.existsSync(dir)) {
1840
+ const entries = fs11.readdirSync(dir);
1601
1841
  if (entries.length === 0) {
1602
- fs10.rmdirSync(dir);
1603
- dir = path11.dirname(dir);
1842
+ fs11.rmdirSync(dir);
1843
+ dir = path12.dirname(dir);
1604
1844
  } else {
1605
1845
  break;
1606
1846
  }
@@ -1749,7 +1989,7 @@ async function main(argv) {
1749
1989
  console.log(USAGE);
1750
1990
  return 0;
1751
1991
  }
1752
- if (process.argv[1] && path12.resolve(fs11.realpathSync(fileURLToPath2(import.meta.url))) === path12.resolve(fs11.realpathSync(process.argv[1]))) {
1992
+ if (process.argv[1] && path13.resolve(fs12.realpathSync(fileURLToPath2(import.meta.url))) === path13.resolve(fs12.realpathSync(process.argv[1]))) {
1753
1993
  main(process.argv).then((code) => process.exit(code)).catch((err) => {
1754
1994
  console.error(err instanceof Error ? err.message : String(err));
1755
1995
  process.exit(1);