@sushanglewis/lincoln 1.5.5 → 1.6.1

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 (81) hide show
  1. package/dist/main.js +425 -51
  2. package/dist/main.js.map +1 -1
  3. package/dist/postinstall.d.ts +9 -2
  4. package/dist/postinstall.js +812 -6
  5. package/dist/postinstall.js.map +1 -1
  6. package/framework/.claude/agents/architect.md +15 -15
  7. package/framework/.claude/agents/default.md +118 -104
  8. package/framework/.claude/agents/designer.md +16 -51
  9. package/framework/.claude/agents/engineer.md +16 -25
  10. package/framework/.claude/agents/frontend-engineer.md +15 -16
  11. package/framework/.claude/agents/knowledge-curator.md +18 -15
  12. package/framework/.claude/agents/pm.md +16 -75
  13. package/framework/.claude/agents/qa.md +17 -13
  14. package/framework/.claude/agents/release-coordinator.md +18 -12
  15. package/framework/.claude/agents/researcher.md +16 -26
  16. package/framework/.claude/harnesses/command-map.yaml +12 -5
  17. package/framework/.claude/hooks/on-session-start.sh +0 -1
  18. package/framework/.claude/schemas/agent-definition.schema.json +6 -1
  19. package/framework/.claude/schemas/stage-definition.schema.json +49 -4
  20. package/framework/.claude/skills/build-product-prototype/prompts/main.md +89 -26
  21. package/framework/.claude/skills/clarify-requirements/prompts/main.md +80 -14
  22. package/framework/.claude/skills/draft-product-design/prompts/main.md +24 -12
  23. package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +2 -0
  24. package/framework/.claude/skills/lc-explore-opensource/SKILL.md +2 -0
  25. package/framework/.claude/skills/lc-market-research/SKILL.md +1 -1
  26. package/framework/.claude/skills/lc-one-id/SKILL.md +81 -0
  27. package/framework/.claude/skills/lc-one-id/prompts/main.md +35 -0
  28. package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +1 -1
  29. package/framework/.claude/skills/lc-workflow-router/SKILL.md +2 -0
  30. package/framework/.claude/stages/build-codebase-knowledge.yaml +10 -10
  31. package/framework/.claude/stages/clarify.yaml +21 -15
  32. package/framework/.claude/stages/explore-opensource.yaml +13 -10
  33. package/framework/.claude/stages/implement.yaml +11 -11
  34. package/framework/.claude/stages/ingest.yaml +13 -10
  35. package/framework/.claude/stages/lc-analyze-frameworks.yaml +13 -10
  36. package/framework/.claude/stages/lc-collect-intelligence.yaml +13 -10
  37. package/framework/.claude/stages/lc-competitive-analysis.yaml +13 -10
  38. package/framework/.claude/stages/lc-first-principles.yaml +13 -10
  39. package/framework/.claude/stages/lc-market-research.yaml +13 -10
  40. package/framework/.claude/stages/lc-product-research.yaml +13 -10
  41. package/framework/.claude/stages/lc-research-report.yaml +13 -10
  42. package/framework/.claude/stages/lc-research-scope.yaml +13 -10
  43. package/framework/.claude/stages/lc-stakeholder-research.yaml +13 -10
  44. package/framework/.claude/stages/lc-storytelling.yaml +13 -10
  45. package/framework/.claude/stages/product-design-docs.yaml +30 -18
  46. package/framework/.claude/stages/product-prototype.yaml +20 -17
  47. package/framework/.claude/stages/propose.yaml +16 -9
  48. package/framework/.claude/stages/split.yaml +13 -9
  49. package/framework/.claude/stages/sync-knowledge.yaml +20 -9
  50. package/framework/.claude/stages/tdd-development-plan.yaml +17 -9
  51. package/framework/.claude/stages/workflow-router.yaml +17 -13
  52. package/framework/.claude/workflows/bug-fix.yaml +1 -0
  53. package/framework/.claude/workflows/existing-project-iteration.yaml +1 -0
  54. package/framework/.claude/workflows/interview-to-knowledge.yaml +1 -0
  55. package/framework/.claude/workflows/oss-first-design.yaml +1 -0
  56. package/framework/.claude-plugin/marketplace.json +2 -2
  57. package/framework/.claude-plugin/plugin.json +2 -1
  58. package/framework/.version-bump.json +1 -1
  59. package/framework/README.en.md +15 -2
  60. package/framework/README.md +16 -3
  61. package/framework/RELEASE.md +32 -0
  62. package/framework/manifest.json +93 -73
  63. package/framework/scripts/check-prompt-drift.py +132 -1
  64. package/framework/scripts/extract_release_notes.py +79 -0
  65. package/framework/scripts/init-lincoln-branch.sh +6 -0
  66. package/framework/scripts/lincoln_command_map.py +1 -1
  67. package/framework/scripts/lincoln_id.py +333 -0
  68. package/framework/scripts/lincoln_index.py +10 -0
  69. package/framework/scripts/lincoln_prd.py +16 -17
  70. package/framework/scripts/lincoln_render.py +657 -0
  71. package/framework/scripts/run-infrastructure-tests.py +2 -0
  72. package/framework/scripts/stage_loader.py +149 -2
  73. package/framework/scripts/static-check.sh +1 -1
  74. package/framework/scripts/update_readme_version.py +149 -0
  75. package/framework/scripts/validate_stage.py +80 -1
  76. package/framework/tools/lincoln/package-lock.json +2 -2
  77. package/framework/tools/lincoln/package.json +1 -1
  78. package/framework/tools/lincoln-installer/package-lock.json +2 -2
  79. package/framework/tools/lincoln-installer/package.json +1 -1
  80. package/package.json +2 -2
  81. package/framework/.claude/agents/_contract.md +0 -79
package/dist/main.js CHANGED
@@ -16,6 +16,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
16
16
  "help",
17
17
  "h",
18
18
  "no-venv",
19
+ "no-interactive",
19
20
  "version",
20
21
  "v"
21
22
  ]);
@@ -400,7 +401,7 @@ function initProject(projectRoot, options = {}) {
400
401
  // src/commands/install.ts
401
402
  import fs7 from "fs";
402
403
  import path8 from "path";
403
- import { spawn as spawn2 } from "child_process";
404
+ import { spawn as spawn3 } from "child_process";
404
405
 
405
406
  // src/lib/syncClaude.ts
406
407
  import fs5 from "fs";
@@ -552,6 +553,112 @@ function* walkSync(dir) {
552
553
  }
553
554
  }
554
555
 
556
+ // src/lib/runHarnessAdapter.ts
557
+ import { spawn as spawn2 } from "child_process";
558
+ function runCommand(command, args, spawnCommand) {
559
+ return new Promise((resolvePromise, rejectPromise) => {
560
+ const child = spawnCommand(command, args, { stdio: "inherit" });
561
+ child.on("close", (code) => {
562
+ resolvePromise(code);
563
+ });
564
+ child.on("error", (err) => {
565
+ rejectPromise(err);
566
+ });
567
+ });
568
+ }
569
+ async function runHarnessAdapter(opts) {
570
+ const report = {
571
+ written: [],
572
+ skipped: [],
573
+ preserved: [],
574
+ warnings: []
575
+ };
576
+ if (opts.dryRun) {
577
+ report.written.push(`${opts.harnessId} (dry run)`);
578
+ return report;
579
+ }
580
+ const args = [
581
+ `${opts.payloadRoot}/scripts/lincoln_harness_adapter.py`,
582
+ "--harness",
583
+ opts.harnessId,
584
+ "--root",
585
+ opts.payloadRoot,
586
+ "--project-dir",
587
+ opts.projectDir,
588
+ "--home-dir",
589
+ opts.homeDir
590
+ ];
591
+ try {
592
+ const spawnCommand = opts.spawnCommand ?? spawn2;
593
+ const code = await runCommand(opts.pythonPath, args, spawnCommand);
594
+ if (code === 0) {
595
+ report.written.push(opts.harnessId);
596
+ } else {
597
+ report.warnings.push(
598
+ `Adapter for ${opts.harnessId} exited with code ${code}. Run manually to diagnose.`
599
+ );
600
+ }
601
+ } catch (err) {
602
+ report.warnings.push(
603
+ `Failed to run adapter for ${opts.harnessId}: ${err instanceof Error ? err.message : String(err)}`
604
+ );
605
+ }
606
+ return report;
607
+ }
608
+
609
+ // src/lib/syncHarness.ts
610
+ function emptyReport() {
611
+ return {
612
+ written: [],
613
+ skipped: [],
614
+ preserved: [],
615
+ warnings: []
616
+ };
617
+ }
618
+ async function syncHarnesses(opts) {
619
+ const reports = {};
620
+ const syncClaude = opts.syncClaude ?? syncClaudeCode;
621
+ for (const harnessId of opts.harnesses) {
622
+ reports[harnessId] = await syncHarness(harnessId, opts, syncClaude);
623
+ }
624
+ return reports;
625
+ }
626
+ async function syncHarness(harnessId, opts, syncClaude) {
627
+ if (harnessId === "claude-code") {
628
+ return syncClaude({
629
+ payloadRoot: opts.payloadRoot,
630
+ targetDir: opts.paths.claudeDir,
631
+ version: opts.version,
632
+ dryRun: opts.dryRun
633
+ });
634
+ }
635
+ if (harnessId === "codex" || harnessId === "opencode") {
636
+ if (opts.dryRun) {
637
+ const report2 = emptyReport();
638
+ report2.written.push(`${harnessId} (dry run)`);
639
+ return report2;
640
+ }
641
+ if (!opts.pythonPath) {
642
+ const report2 = emptyReport();
643
+ report2.warnings.push(
644
+ `Skipped ${harnessId} sync because Python is not available. Install Python 3.10+ or run without --no-venv.`
645
+ );
646
+ return report2;
647
+ }
648
+ return runHarnessAdapter({
649
+ harnessId,
650
+ payloadRoot: opts.payloadRoot,
651
+ projectDir: opts.projectDir,
652
+ homeDir: opts.paths.homeDir,
653
+ pythonPath: opts.pythonPath,
654
+ dryRun: opts.dryRun
655
+ });
656
+ }
657
+ const report = emptyReport();
658
+ report.warnings.push(`Unsupported harness: ${harnessId}`);
659
+ return report;
660
+ }
661
+
555
662
  // src/lib/harnessDetect.ts
556
663
  import fs6 from "fs";
557
664
  import path7 from "path";
@@ -582,39 +689,218 @@ function detectGlobalHarnesses(overrideHomeDir) {
582
689
  function installedHarnessIds(homeDir) {
583
690
  return detectGlobalHarnesses(homeDir).filter((h) => h.installed).map((h) => h.id);
584
691
  }
692
+ var HARNESS_IDS = HARNESS_SPECS.map((spec) => spec.id);
693
+ function isValidHarnessId(value) {
694
+ return HARNESS_SPECS.some((spec) => spec.id === value);
695
+ }
696
+
697
+ // src/lib/harnessSelect.ts
698
+ var HARNESS_LABELS = {
699
+ "claude-code": "Claude Code",
700
+ codex: "Codex",
701
+ opencode: "OpenCode"
702
+ };
703
+ function buildHarnessOptions(detected) {
704
+ return detected.map((harness) => ({
705
+ id: harness.id,
706
+ label: HARNESS_LABELS[harness.id],
707
+ checked: harness.installed
708
+ }));
709
+ }
710
+ function resolveHarnessSelection(detected, selection) {
711
+ if (selection === void 0) {
712
+ return detected.filter((harness) => harness.installed).map((harness) => harness.id);
713
+ }
714
+ const validIds = /* @__PURE__ */ new Set();
715
+ for (const id of selection) {
716
+ const match = detected.find((harness) => harness.id === id);
717
+ if (match) {
718
+ validIds.add(match.id);
719
+ }
720
+ }
721
+ return Array.from(validIds);
722
+ }
723
+
724
+ // src/lib/prompt.ts
725
+ function createLineReader(input, output) {
726
+ const buffer = [];
727
+ const queue = [];
728
+ let remainder = "";
729
+ let ended = false;
730
+ function flushQueue() {
731
+ while (queue.length > 0 && buffer.length > 0) {
732
+ const next = queue.shift();
733
+ const line = buffer.shift();
734
+ if (next && line !== void 0) {
735
+ next.resolve(line);
736
+ }
737
+ }
738
+ }
739
+ input.on("data", (chunk) => {
740
+ const text = remainder + chunk.toString("utf-8");
741
+ const lines = text.split("\n");
742
+ remainder = lines.pop() ?? "";
743
+ for (const line of lines) {
744
+ buffer.push(line.replace(/\r$/, ""));
745
+ }
746
+ flushQueue();
747
+ });
748
+ input.on("end", () => {
749
+ ended = true;
750
+ if (remainder.length > 0) {
751
+ buffer.push(remainder.replace(/\r$/, ""));
752
+ remainder = "";
753
+ }
754
+ flushQueue();
755
+ while (queue.length > 0) {
756
+ const next = queue.shift();
757
+ next?.resolve("");
758
+ }
759
+ });
760
+ input.on("error", (err) => {
761
+ while (queue.length > 0) {
762
+ queue.shift()?.reject(err);
763
+ }
764
+ });
765
+ return {
766
+ readLine(prompt) {
767
+ output.write(prompt);
768
+ if (buffer.length > 0) {
769
+ return Promise.resolve(buffer.shift() ?? "");
770
+ }
771
+ if (ended) {
772
+ return Promise.resolve("");
773
+ }
774
+ return new Promise((resolve, reject) => {
775
+ queue.push({ resolve, reject });
776
+ });
777
+ },
778
+ close() {
779
+ }
780
+ };
781
+ }
782
+ function normalizeAnswer(text) {
783
+ return text.trim().toLowerCase();
784
+ }
785
+ function createPrompt(streams = {}) {
786
+ const input = streams.input ?? process.stdin;
787
+ const output = streams.output ?? process.stdout;
788
+ const reader = createLineReader(input, output);
789
+ async function ask(question) {
790
+ return reader.readLine(question);
791
+ }
792
+ return {
793
+ async confirm(question, defaultValue = true) {
794
+ const hint = defaultValue ? "[Y/n]" : "[y/N]";
795
+ const first = await ask(`${question} ${hint} `);
796
+ const normalized = normalizeAnswer(first);
797
+ if (normalized === "y" || normalized === "yes") {
798
+ return true;
799
+ }
800
+ if (normalized === "n" || normalized === "no") {
801
+ return false;
802
+ }
803
+ if (normalized === "") {
804
+ return defaultValue;
805
+ }
806
+ const second = await ask("Please answer y or n: ");
807
+ const secondNormalized = normalizeAnswer(second);
808
+ if (secondNormalized === "y" || secondNormalized === "yes") {
809
+ return true;
810
+ }
811
+ if (secondNormalized === "n" || secondNormalized === "no") {
812
+ return false;
813
+ }
814
+ return defaultValue;
815
+ },
816
+ async multiSelect(question, options) {
817
+ const lines = options.map((opt, index) => {
818
+ const mark = opt.checked ? "x" : " ";
819
+ return ` [${mark}] ${index + 1}. ${opt.label}`;
820
+ });
821
+ const promptText = `${question}
822
+ ${lines.join("\n")}
823
+ Enter numbers (e.g. 1,3), 'all', 'none', or harness names: `;
824
+ async function parse(answer) {
825
+ const normalized = normalizeAnswer(answer);
826
+ if (normalized === "all") {
827
+ return options.map((opt) => opt.id);
828
+ }
829
+ if (normalized === "none") {
830
+ return [];
831
+ }
832
+ if (normalized === "") {
833
+ return options.filter((opt) => opt.checked).map((opt) => opt.id);
834
+ }
835
+ const parts = normalized.split(/[,\s]+/).filter((part) => part.length > 0);
836
+ const selected = /* @__PURE__ */ new Set();
837
+ for (const part of parts) {
838
+ const index = Number(part);
839
+ if (!Number.isNaN(index) && index >= 1 && index <= options.length) {
840
+ selected.add(options[index - 1].id);
841
+ continue;
842
+ }
843
+ const match = options.find((opt) => opt.id === part);
844
+ if (match) {
845
+ selected.add(match.id);
846
+ continue;
847
+ }
848
+ return void 0;
849
+ }
850
+ return Array.from(selected);
851
+ }
852
+ let result = await parse(await ask(promptText));
853
+ if (result !== void 0) {
854
+ return result;
855
+ }
856
+ const retry = await ask("Invalid selection. Please try again: ");
857
+ result = await parse(retry);
858
+ return result ?? [];
859
+ },
860
+ close() {
861
+ reader.close();
862
+ }
863
+ };
864
+ }
585
865
 
586
866
  // src/commands/install.ts
587
867
  function createDefaultDeps2() {
588
868
  return {
589
869
  paths: resolveLincolnPaths(),
590
870
  payloadRoot: resolvePayloadRoot() || "",
591
- syncClaude: syncClaudeCode
871
+ syncHarnesses,
872
+ createPrompt,
873
+ resolvePythonForVenv,
874
+ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY)
592
875
  };
593
876
  }
594
877
  async function install(options, deps = createDefaultDeps2()) {
595
- if (!options.dryRun && !options.yes) {
596
- console.error("This will install Lincoln globally. Run with --yes to proceed.");
597
- return 1;
598
- }
599
878
  const version = readLocalPackageVersion();
600
879
  if (!version) {
601
880
  console.error("Could not determine Lincoln version");
602
881
  return 1;
603
882
  }
604
- const harnesses = options.harnesses.length > 0 ? options.harnesses : installedHarnessIds(deps.paths.homeDir);
605
- if (harnesses.length === 0) {
606
- console.error("No agent harness detected. Install Claude Code, Codex, or OpenCode first.");
607
- return 1;
608
- }
609
883
  const payloadRoot = deps.payloadRoot;
610
884
  if (!payloadRoot || !fs7.existsSync(payloadRoot)) {
885
+ console.error('Lincoln payload not found. Run "npm install -g @sushanglewis/lincoln" first.');
886
+ return 1;
887
+ }
888
+ if (!options.dryRun && !options.yes && (options.noInteractive || !deps.isTTY)) {
611
889
  console.error(
612
- `Lincoln payload not found. Run "npm install -g @sushanglewis/lincoln" first.`
890
+ "This will install Lincoln globally. Run with --yes to proceed, or run interactively in a TTY."
613
891
  );
614
892
  return 1;
615
893
  }
616
- const versionDir = path8.join(deps.paths.versionsDir, version);
894
+ const harnesses = await resolveHarnesses(options, deps);
895
+ if (harnesses.length === 0) {
896
+ return 1;
897
+ }
898
+ const pythonPath = await resolvePythonIfNeeded(options, deps, harnesses);
899
+ if (pythonPath === null) {
900
+ return 1;
901
+ }
617
902
  if (!options.dryRun) {
903
+ const versionDir = path8.join(deps.paths.versionsDir, version);
618
904
  fs7.mkdirSync(versionDir, { recursive: true });
619
905
  fs7.cpSync(payloadRoot, versionDir, { recursive: true, force: true });
620
906
  try {
@@ -625,42 +911,111 @@ async function install(options, deps = createDefaultDeps2()) {
625
911
  fs7.cpSync(versionDir, deps.paths.currentDir, { recursive: true, force: true });
626
912
  }
627
913
  }
628
- const report = deps.syncClaude({
914
+ const reports = await deps.syncHarnesses({
915
+ harnesses,
629
916
  payloadRoot: deps.paths.currentDir,
630
- targetDir: deps.paths.claudeDir,
917
+ paths: deps.paths,
918
+ projectDir: deps.paths.lincolnHome,
631
919
  version,
632
- dryRun: options.dryRun
920
+ dryRun: options.dryRun,
921
+ pythonPath
633
922
  });
634
- if (!options.dryRun && !options.noVenv) {
635
- const python = await resolvePythonForVenv();
636
- if (!python) {
637
- console.error(
638
- "Python 3.10+ is required but was not found. Set LINCOLN_PYTHON to a compatible Python executable."
639
- );
640
- return 1;
641
- }
642
- await setupVenv(deps.paths, payloadRoot, python);
643
- }
644
923
  if (!options.dryRun) {
645
924
  writeVersionMarker(deps.paths, {
646
925
  version,
647
926
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
648
927
  harnesses,
649
- managedFiles: report.written
928
+ managedFiles: collectManagedFiles(reports)
650
929
  });
651
930
  }
931
+ printSummary(options, version, harnesses, reports);
932
+ return 0;
933
+ }
934
+ async function resolvePythonIfNeeded(options, deps, harnesses) {
935
+ const needsPython = !options.dryRun && !options.noVenv && harnesses.some((id) => id === "codex" || id === "opencode");
936
+ if (!needsPython) {
937
+ return void 0;
938
+ }
939
+ const pythonCommand = await deps.resolvePythonForVenv();
940
+ if (!pythonCommand) {
941
+ console.error(
942
+ "Python 3.10+ is required for Codex/OpenCode sync but was not found. Set LINCOLN_PYTHON to a compatible Python executable, or run with --no-venv."
943
+ );
944
+ return null;
945
+ }
946
+ await setupVenv(deps.paths, deps.payloadRoot, pythonCommand);
947
+ return venvPythonPath(deps.paths);
948
+ }
949
+ async function resolveHarnesses(options, deps) {
950
+ const detected = detectGlobalHarnesses(deps.paths.homeDir);
951
+ if (options.harnesses.length > 0) {
952
+ const invalid = options.harnesses.filter((id) => !isValidHarnessId(id));
953
+ if (invalid.length > 0) {
954
+ console.error(`Unknown harness id(s): ${invalid.join(", ")}`);
955
+ console.error(`Valid ids: ${HARNESS_IDS.join(", ")}`);
956
+ return [];
957
+ }
958
+ return options.harnesses;
959
+ }
960
+ const selected = installedHarnessIds(deps.paths.homeDir);
961
+ if (selected.length > 0 && !options.dryRun && !options.yes && !options.noInteractive && deps.isTTY) {
962
+ const prompt = deps.createPrompt();
963
+ try {
964
+ const answer = await prompt.multiSelect(
965
+ "Select agent harnesses to install Lincoln into:",
966
+ buildHarnessOptions(detected)
967
+ );
968
+ const resolved = resolveHarnessSelection(detected, answer);
969
+ if (resolved.length === 0) {
970
+ console.error("No harnesses selected. Aborting.");
971
+ return [];
972
+ }
973
+ return resolved;
974
+ } catch (err) {
975
+ console.error(`Interactive selection failed: ${err instanceof Error ? err.message : String(err)}`);
976
+ return [];
977
+ } finally {
978
+ prompt.close();
979
+ }
980
+ }
981
+ if (selected.length === 0 && !options.dryRun) {
982
+ console.error("No agent harness detected. Install Claude Code, Codex, or OpenCode first.");
983
+ return [];
984
+ }
985
+ return selected;
986
+ }
987
+ function collectManagedFiles(reports) {
988
+ const files = [];
989
+ for (const report of Object.values(reports)) {
990
+ files.push(...report.written, ...report.skipped, ...report.preserved);
991
+ }
992
+ return files;
993
+ }
994
+ function printSummary(options, version, harnesses, reports) {
995
+ const warnings = collectWarnings(reports);
652
996
  if (options.dryRun) {
653
997
  console.log(`Dry run: would install Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
654
- console.log(`Would write ${report.written.length} files`);
655
998
  } else {
656
999
  console.log(`Installed Lincoln ${version} for harnesses: ${harnesses.join(", ")}`);
657
- console.log(`Wrote ${report.written.length} files`);
658
1000
  }
659
- return 0;
1001
+ for (const [harnessId, report] of Object.entries(reports)) {
1002
+ const count = report.written.length + report.skipped.length + report.preserved.length;
1003
+ console.log(` ${harnessId}: ${count} file(s)`);
1004
+ }
1005
+ for (const warning of warnings) {
1006
+ console.warn(` warning: ${warning}`);
1007
+ }
660
1008
  }
661
- function runCommand(command, args) {
1009
+ function collectWarnings(reports) {
1010
+ const warnings = [];
1011
+ for (const report of Object.values(reports)) {
1012
+ warnings.push(...report.warnings);
1013
+ }
1014
+ return warnings;
1015
+ }
1016
+ function runCommand2(command, args) {
662
1017
  return new Promise((resolvePromise, rejectPromise) => {
663
- const child = spawn2(command, args, { stdio: "inherit" });
1018
+ const child = spawn3(command, args, { stdio: "inherit" });
664
1019
  child.on("close", (code) => resolvePromise(code ?? 1));
665
1020
  child.on("error", (err) => rejectPromise(err));
666
1021
  });
@@ -677,7 +1032,7 @@ function parsePythonVersion2(version) {
677
1032
  }
678
1033
  async function queryPythonVersion(command) {
679
1034
  return new Promise((resolvePromise) => {
680
- const child = spawn2(command, ["--version"], {
1035
+ const child = spawn3(command, ["--version"], {
681
1036
  stdio: ["ignore", "pipe", "ignore"],
682
1037
  shell: process.platform === "win32"
683
1038
  });
@@ -706,15 +1061,21 @@ async function resolvePythonForVenv(envOverride = process.env.LINCOLN_PYTHON, ca
706
1061
  }
707
1062
  return void 0;
708
1063
  }
709
- async function setupVenv(paths, payloadRoot, pythonCommand) {
710
- const pythonExe = path8.join(
1064
+ function venvPythonPath(paths) {
1065
+ return path8.join(
711
1066
  paths.venvDir,
712
1067
  process.platform === "win32" ? "Scripts" : "bin",
713
1068
  process.platform === "win32" ? "python.exe" : "python"
714
1069
  );
1070
+ }
1071
+ async function setupVenv(paths, payloadRoot, pythonCommand) {
1072
+ const pythonExe = venvPythonPath(paths);
715
1073
  if (!fs7.existsSync(pythonExe)) {
716
1074
  console.log("Creating Lincoln virtual environment...");
717
- await runCommand(pythonCommand, ["-m", "venv", paths.venvDir]);
1075
+ const venvCode = await runCommand2(pythonCommand, ["-m", "venv", paths.venvDir]);
1076
+ if (venvCode !== 0) {
1077
+ throw new Error(`Failed to create virtual environment (exit code ${venvCode})`);
1078
+ }
718
1079
  }
719
1080
  const requirementsPath = path8.join(payloadRoot, "requirements.txt");
720
1081
  if (fs7.existsSync(requirementsPath)) {
@@ -724,12 +1085,15 @@ async function setupVenv(paths, payloadRoot, pythonCommand) {
724
1085
  process.platform === "win32" ? "Scripts" : "bin",
725
1086
  process.platform === "win32" ? "pip.exe" : "pip"
726
1087
  );
727
- await runCommand(pipExe, ["install", "-r", requirementsPath]);
1088
+ const pipCode = await runCommand2(pipExe, ["install", "-r", requirementsPath]);
1089
+ if (pipCode !== 0) {
1090
+ throw new Error(`Failed to install Python dependencies (exit code ${pipCode})`);
1091
+ }
728
1092
  }
729
1093
  }
730
1094
 
731
1095
  // src/commands/update.ts
732
- import { spawn as spawn3 } from "child_process";
1096
+ import { spawn as spawn4 } from "child_process";
733
1097
 
734
1098
  // src/lib/npmRegistry.ts
735
1099
  var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
@@ -897,13 +1261,14 @@ async function update(options, deps = {}) {
897
1261
  return 0;
898
1262
  }
899
1263
  function defaultInstallOptions() {
900
- return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true };
1264
+ return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true, noInteractive: true };
901
1265
  }
902
1266
  function runNpmInstallGlobal(packageName, version) {
903
1267
  return new Promise((resolvePromise) => {
904
- const child = spawn3("npm", ["install", "-g", `${packageName}@${version}`], {
1268
+ const child = spawn4("npm", ["install", "-g", `${packageName}@${version}`], {
905
1269
  stdio: "inherit",
906
- shell: process.platform === "win32"
1270
+ shell: process.platform === "win32",
1271
+ env: { ...process.env, LINCOLN_SKIP_POSTINSTALL: "1" }
907
1272
  });
908
1273
  child.on("close", (code) => resolvePromise(code ?? 1));
909
1274
  child.on("error", () => resolvePromise(1));
@@ -913,7 +1278,7 @@ function runNpmInstallGlobal(packageName, version) {
913
1278
  // src/commands/use.ts
914
1279
  import fs8 from "fs";
915
1280
  import path9 from "path";
916
- import { spawn as spawn4 } from "child_process";
1281
+ import { spawn as spawn5 } from "child_process";
917
1282
  var PACKAGE_NAME2 = "@sushanglewis/lincoln";
918
1283
  var SEMVER_REGEX = /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
919
1284
  var STRICT_SEMVER_REGEX = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -1042,13 +1407,14 @@ function switchCurrentLink(paths, versionDir) {
1042
1407
  }
1043
1408
  }
1044
1409
  function defaultInstallOptions2() {
1045
- return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true };
1410
+ return { yes: true, dryRun: false, force: false, harnesses: [], noVenv: true, noInteractive: true };
1046
1411
  }
1047
1412
  function runNpmInstallGlobal2(packageName, version) {
1048
1413
  return new Promise((resolvePromise) => {
1049
- const child = spawn4("npm", ["install", "-g", `${packageName}@${version}`], {
1414
+ const child = spawn5("npm", ["install", "-g", `${packageName}@${version}`], {
1050
1415
  stdio: "inherit",
1051
- shell: process.platform === "win32"
1416
+ shell: process.platform === "win32",
1417
+ env: { ...process.env, LINCOLN_SKIP_POSTINSTALL: "1" }
1052
1418
  });
1053
1419
  child.on("close", (code) => resolvePromise(code ?? 1));
1054
1420
  child.on("error", () => resolvePromise(1));
@@ -1056,7 +1422,7 @@ function runNpmInstallGlobal2(packageName, version) {
1056
1422
  }
1057
1423
  function runNpmRootGlobal() {
1058
1424
  return new Promise((resolvePromise) => {
1059
- const child = spawn4("npm", ["root", "-g"], {
1425
+ const child = spawn5("npm", ["root", "-g"], {
1060
1426
  stdio: ["ignore", "pipe", "ignore"],
1061
1427
  shell: process.platform === "win32"
1062
1428
  });
@@ -1274,10 +1640,10 @@ async function migrateProject(projectRoot, options = {}, deps = createDefaultDep
1274
1640
  }
1275
1641
 
1276
1642
  // src/commands/record.ts
1277
- import { spawn as spawn5 } from "child_process";
1643
+ import { spawn as spawn6 } from "child_process";
1278
1644
  import { EventEmitter } from "events";
1279
1645
  function createDefaultDeps6() {
1280
- return { spawn: spawn5 };
1646
+ return { spawn: spawn6 };
1281
1647
  }
1282
1648
  function record(args, deps = createDefaultDeps6()) {
1283
1649
  return new Promise((resolve) => {
@@ -1312,6 +1678,8 @@ Options:
1312
1678
  --yes, -y Skip confirmation prompts
1313
1679
  --dry-run Show what would be done without making changes
1314
1680
  --force Force re-install even if up to date
1681
+ --harnesses Comma-separated harness ids (claude-code, codex, opencode)
1682
+ --no-interactive Never prompt; fail if --yes is also missing
1315
1683
  --check Check for available updates without installing (update)
1316
1684
  --json Output machine-readable JSON (where supported)
1317
1685
  --help, -h Show this help message
@@ -1329,12 +1697,15 @@ async function main(argv) {
1329
1697
  return 0;
1330
1698
  }
1331
1699
  if (parsed.command === "install") {
1700
+ const harnessesFlag = typeof parsed.flags.harnesses === "string" ? String(parsed.flags.harnesses).split(",").map((id) => id.trim()).filter((id) => id.length > 0) : [];
1701
+ const harnesses = harnessesFlag.length > 0 ? harnessesFlag : parsed.args;
1332
1702
  return install({
1333
1703
  yes: Boolean(parsed.flags.yes || parsed.flags.y),
1334
1704
  dryRun: Boolean(parsed.flags["dry-run"]),
1335
1705
  force: Boolean(parsed.flags.force),
1336
- harnesses: parsed.args,
1337
- noVenv: Boolean(parsed.flags["no-venv"])
1706
+ harnesses,
1707
+ noVenv: Boolean(parsed.flags["no-venv"]),
1708
+ noInteractive: Boolean(parsed.flags["no-interactive"])
1338
1709
  });
1339
1710
  }
1340
1711
  if (parsed.command === "update") {
@@ -1379,7 +1750,10 @@ async function main(argv) {
1379
1750
  return 0;
1380
1751
  }
1381
1752
  if (process.argv[1] && path12.resolve(fs11.realpathSync(fileURLToPath2(import.meta.url))) === path12.resolve(fs11.realpathSync(process.argv[1]))) {
1382
- main(process.argv).then((code) => process.exit(code));
1753
+ main(process.argv).then((code) => process.exit(code)).catch((err) => {
1754
+ console.error(err instanceof Error ? err.message : String(err));
1755
+ process.exit(1);
1756
+ });
1383
1757
  }
1384
1758
  export {
1385
1759
  main