@sushanglewis/lincoln 1.5.5 → 1.6.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.
- package/dist/main.js +425 -51
- package/dist/main.js.map +1 -1
- package/dist/postinstall.d.ts +9 -2
- package/dist/postinstall.js +812 -6
- package/dist/postinstall.js.map +1 -1
- package/framework/.claude/agents/architect.md +15 -15
- package/framework/.claude/agents/default.md +118 -104
- package/framework/.claude/agents/designer.md +16 -51
- package/framework/.claude/agents/engineer.md +16 -25
- package/framework/.claude/agents/frontend-engineer.md +15 -16
- package/framework/.claude/agents/knowledge-curator.md +18 -15
- package/framework/.claude/agents/pm.md +16 -75
- package/framework/.claude/agents/qa.md +17 -13
- package/framework/.claude/agents/release-coordinator.md +18 -12
- package/framework/.claude/agents/researcher.md +16 -26
- package/framework/.claude/harnesses/command-map.yaml +13 -6
- package/framework/.claude/hooks/on-session-start.sh +0 -1
- package/framework/.claude/schemas/agent-definition.schema.json +6 -1
- package/framework/.claude/schemas/stage-definition.schema.json +49 -4
- package/framework/.claude/skills/build-product-prototype/prompts/main.md +89 -26
- package/framework/.claude/skills/clarify-requirements/prompts/main.md +80 -14
- package/framework/.claude/skills/draft-product-design/prompts/main.md +24 -12
- package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +2 -0
- package/framework/.claude/skills/lc-explore-opensource/SKILL.md +2 -0
- package/framework/.claude/skills/lc-init-branch/SKILL.md +2 -2
- package/framework/.claude/skills/lc-market-research/SKILL.md +1 -1
- package/framework/.claude/skills/lc-one-id/SKILL.md +81 -0
- package/framework/.claude/skills/lc-one-id/prompts/main.md +35 -0
- package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +1 -1
- package/framework/.claude/skills/lc-workflow-router/SKILL.md +2 -0
- package/framework/.claude/stages/build-codebase-knowledge.yaml +10 -10
- package/framework/.claude/stages/clarify.yaml +21 -15
- package/framework/.claude/stages/explore-opensource.yaml +13 -10
- package/framework/.claude/stages/implement.yaml +11 -11
- package/framework/.claude/stages/ingest.yaml +13 -10
- package/framework/.claude/stages/lc-analyze-frameworks.yaml +13 -10
- package/framework/.claude/stages/lc-collect-intelligence.yaml +13 -10
- package/framework/.claude/stages/lc-competitive-analysis.yaml +13 -10
- package/framework/.claude/stages/lc-first-principles.yaml +13 -10
- package/framework/.claude/stages/lc-market-research.yaml +13 -10
- package/framework/.claude/stages/lc-product-research.yaml +13 -10
- package/framework/.claude/stages/lc-research-report.yaml +13 -10
- package/framework/.claude/stages/lc-research-scope.yaml +13 -10
- package/framework/.claude/stages/lc-stakeholder-research.yaml +13 -10
- package/framework/.claude/stages/lc-storytelling.yaml +13 -10
- package/framework/.claude/stages/product-design-docs.yaml +30 -18
- package/framework/.claude/stages/product-prototype.yaml +20 -17
- package/framework/.claude/stages/propose.yaml +16 -9
- package/framework/.claude/stages/split.yaml +13 -9
- package/framework/.claude/stages/sync-knowledge.yaml +20 -9
- package/framework/.claude/stages/tdd-development-plan.yaml +17 -9
- package/framework/.claude/stages/workflow-router.yaml +17 -13
- package/framework/.claude/workflows/bug-fix.yaml +1 -0
- package/framework/.claude/workflows/existing-project-iteration.yaml +1 -0
- package/framework/.claude/workflows/interview-to-knowledge.yaml +1 -0
- package/framework/.claude/workflows/oss-first-design.yaml +1 -0
- package/framework/.claude-plugin/marketplace.json +2 -2
- package/framework/.claude-plugin/plugin.json +2 -1
- package/framework/.version-bump.json +1 -1
- package/framework/README.en.md +15 -2
- package/framework/README.md +16 -3
- package/framework/RELEASE.md +48 -0
- package/framework/manifest.json +94 -74
- package/framework/scripts/check-prompt-drift.py +132 -1
- package/framework/scripts/extract_release_notes.py +79 -0
- package/framework/scripts/init-lincoln-branch.sh +6 -0
- package/framework/scripts/lincoln_command_map.py +1 -1
- package/framework/scripts/lincoln_id.py +333 -0
- package/framework/scripts/lincoln_index.py +10 -0
- package/framework/scripts/lincoln_prd.py +16 -17
- package/framework/scripts/lincoln_render.py +657 -0
- package/framework/scripts/run-infrastructure-tests.py +2 -0
- package/framework/scripts/stage_loader.py +149 -2
- package/framework/scripts/static-check.sh +1 -1
- package/framework/scripts/update_readme_version.py +149 -0
- package/framework/scripts/validate_stage.py +80 -1
- package/framework/tools/lincoln/package-lock.json +2 -2
- package/framework/tools/lincoln/package.json +1 -1
- package/framework/tools/lincoln-installer/package-lock.json +2 -2
- package/framework/tools/lincoln-installer/package.json +1 -1
- package/package.json +2 -2
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
914
|
+
const reports = await deps.syncHarnesses({
|
|
915
|
+
harnesses,
|
|
629
916
|
payloadRoot: deps.paths.currentDir,
|
|
630
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
710
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
1643
|
+
import { spawn as spawn6 } from "child_process";
|
|
1278
1644
|
import { EventEmitter } from "events";
|
|
1279
1645
|
function createDefaultDeps6() {
|
|
1280
|
-
return { spawn:
|
|
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
|
|
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
|