@sushanglewis/lincoln 1.5.0 → 1.5.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 (305) hide show
  1. package/dist/main.d.ts +3 -0
  2. package/dist/main.js +1231 -906
  3. package/dist/main.js.map +1 -1
  4. package/dist/postinstall.d.ts +3 -0
  5. package/dist/postinstall.js +18 -0
  6. package/dist/postinstall.js.map +1 -0
  7. package/framework/.claude/agents/_contract.md +79 -0
  8. package/framework/.claude/agents/architect.md +31 -0
  9. package/framework/.claude/agents/default.md +151 -0
  10. package/framework/.claude/agents/designer.md +33 -0
  11. package/framework/.claude/agents/engineer.md +42 -0
  12. package/framework/.claude/agents/external/NOTICES.md +12 -0
  13. package/framework/.claude/agents/external/everything-claude-code.manifest.yaml +23 -0
  14. package/framework/.claude/agents/external/oh-my-claudecode.manifest.yaml +26 -0
  15. package/framework/.claude/agents/external/wshobson-agents.manifest.yaml +23 -0
  16. package/framework/.claude/agents/frontend-engineer.md +32 -0
  17. package/framework/.claude/agents/knowledge-curator.md +28 -0
  18. package/framework/.claude/agents/pm.md +38 -0
  19. package/framework/.claude/agents/qa.md +28 -0
  20. package/framework/.claude/agents/release-coordinator.md +25 -0
  21. package/framework/.claude/agents/researcher.md +41 -0
  22. package/framework/.claude/harnesses/claude-code.yaml +15 -0
  23. package/framework/.claude/harnesses/codex.yaml +40 -0
  24. package/framework/.claude/harnesses/command-map.yaml +255 -0
  25. package/framework/.claude/harnesses/opencode.yaml +32 -0
  26. package/framework/.claude/harnesses/scenarios.yaml +45 -0
  27. package/framework/.claude/hooks/on-pr-event.sh +69 -0
  28. package/framework/.claude/hooks/on-session-start.sh +427 -0
  29. package/framework/.claude/hooks/on-stop.sh +104 -0
  30. package/framework/.claude/hooks/post-tool-use.sh +161 -0
  31. package/framework/.claude/hooks/pre-tool-use.sh +246 -0
  32. package/framework/.claude/schemas/agent-definition.schema.json +35 -0
  33. package/framework/.claude/schemas/benchmark-report.schema.json +148 -0
  34. package/framework/.claude/schemas/pm-to-ux-handoff.schema.yaml +128 -0
  35. package/framework/.claude/schemas/stage-definition.schema.json +233 -0
  36. package/framework/.claude/schemas/workflow-stage.schema.json +86 -0
  37. package/framework/.claude/schemas/workflow-template.schema.json +88 -0
  38. package/framework/.claude/settings.json +14 -0
  39. package/framework/.claude/skills/build-product-prototype/SKILL.md +35 -0
  40. package/framework/.claude/skills/build-product-prototype/prompts/main.md +78 -0
  41. package/framework/.claude/skills/clarify-requirements/SKILL.md +32 -0
  42. package/framework/.claude/skills/clarify-requirements/prompts/main.md +81 -0
  43. package/framework/.claude/skills/dependencies.yaml +139 -0
  44. package/framework/.claude/skills/draft-product-design/SKILL.md +40 -0
  45. package/framework/.claude/skills/draft-product-design/prompts/main.md +47 -0
  46. package/framework/.claude/skills/lc-analyze-frameworks/SKILL.md +43 -0
  47. package/framework/.claude/skills/lc-analyze-frameworks/prompts/main.md +95 -0
  48. package/framework/.claude/skills/lc-benchmark/SKILL.md +61 -0
  49. package/framework/.claude/skills/lc-build-codebase-knowledge/SKILL.md +38 -0
  50. package/framework/.claude/skills/lc-build-codebase-knowledge/prompts/build-codebase-knowledge.md +61 -0
  51. package/framework/.claude/skills/lc-collect-intelligence/SKILL.md +43 -0
  52. package/framework/.claude/skills/lc-collect-intelligence/prompts/main.md +72 -0
  53. package/framework/.claude/skills/lc-competitive-analysis/SKILL.md +43 -0
  54. package/framework/.claude/skills/lc-competitive-analysis/prompts/main.md +70 -0
  55. package/framework/.claude/skills/lc-explore-opensource/SKILL.md +36 -0
  56. package/framework/.claude/skills/lc-explore-opensource/prompts/explore-opensource.md +77 -0
  57. package/framework/.claude/skills/lc-first-principles/SKILL.md +43 -0
  58. package/framework/.claude/skills/lc-first-principles/prompts/main.md +73 -0
  59. package/framework/.claude/skills/lc-handoff/SKILL.md +34 -0
  60. package/framework/.claude/skills/lc-init-branch/SKILL.md +52 -0
  61. package/framework/.claude/skills/lc-market-research/SKILL.md +43 -0
  62. package/framework/.claude/skills/lc-market-research/prompts/main.md +79 -0
  63. package/framework/.claude/skills/lc-product-research/SKILL.md +43 -0
  64. package/framework/.claude/skills/lc-product-research/prompts/main.md +80 -0
  65. package/framework/.claude/skills/lc-research-report/SKILL.md +43 -0
  66. package/framework/.claude/skills/lc-research-report/prompts/main.md +90 -0
  67. package/framework/.claude/skills/lc-research-scope/SKILL.md +43 -0
  68. package/framework/.claude/skills/lc-research-scope/prompts/main.md +82 -0
  69. package/framework/.claude/skills/lc-setup/SKILL.md +68 -0
  70. package/framework/.claude/skills/lc-setup/prompts/main.md +88 -0
  71. package/framework/.claude/skills/lc-stage/SKILL.md +45 -0
  72. package/framework/.claude/skills/lc-stakeholder-research/SKILL.md +43 -0
  73. package/framework/.claude/skills/lc-stakeholder-research/prompts/main.md +65 -0
  74. package/framework/.claude/skills/lc-status/SKILL.md +25 -0
  75. package/framework/.claude/skills/lc-storytelling/SKILL.md +43 -0
  76. package/framework/.claude/skills/lc-storytelling/prompts/main.md +77 -0
  77. package/framework/.claude/skills/lc-wf/SKILL.md +54 -0
  78. package/framework/.claude/skills/lc-workflow/SKILL.md +48 -0
  79. package/framework/.claude/skills/lc-workflow/validators/validate.py +755 -0
  80. package/framework/.claude/skills/lc-workflow-router/SKILL.md +41 -0
  81. package/framework/.claude/skills/lc-workflow-router/prompts/intake-prompt.md +91 -0
  82. package/framework/.claude/skills/lc-workflow-router/prompts/router-prompt.md +44 -0
  83. package/framework/.claude/skills/plan-tdd-development/SKILL.md +31 -0
  84. package/framework/.claude/skills/plan-tdd-development/prompts/main.md +44 -0
  85. package/framework/.claude/skills/process-interview/SKILL.md +32 -0
  86. package/framework/.claude/skills/process-interview/prompts/main.md +47 -0
  87. package/framework/.claude/skills/propose-with-openspec/SKILL.md +37 -0
  88. package/framework/.claude/skills/propose-with-openspec/prompts/main.md +53 -0
  89. package/framework/.claude/skills/split-to-github/SKILL.md +32 -0
  90. package/framework/.claude/skills/split-to-github/prompts/main.md +42 -0
  91. package/framework/.claude/skills/sync-to-knowledge/SKILL.md +35 -0
  92. package/framework/.claude/skills/sync-to-knowledge/prompts/main.md +54 -0
  93. package/framework/.claude/skills/workflow-continue/SKILL.md +24 -0
  94. package/framework/.claude/skills/workflow-continue/prompts/main.md +25 -0
  95. package/framework/.claude/stages/build-codebase-knowledge.yaml +42 -0
  96. package/framework/.claude/stages/clarify.yaml +59 -0
  97. package/framework/.claude/stages/explore-opensource.yaml +40 -0
  98. package/framework/.claude/stages/implement.yaml +60 -0
  99. package/framework/.claude/stages/ingest.yaml +40 -0
  100. package/framework/.claude/stages/lc-analyze-frameworks.yaml +37 -0
  101. package/framework/.claude/stages/lc-collect-intelligence.yaml +37 -0
  102. package/framework/.claude/stages/lc-competitive-analysis.yaml +37 -0
  103. package/framework/.claude/stages/lc-first-principles.yaml +37 -0
  104. package/framework/.claude/stages/lc-market-research.yaml +37 -0
  105. package/framework/.claude/stages/lc-product-research.yaml +37 -0
  106. package/framework/.claude/stages/lc-research-report.yaml +38 -0
  107. package/framework/.claude/stages/lc-research-scope.yaml +38 -0
  108. package/framework/.claude/stages/lc-stakeholder-research.yaml +37 -0
  109. package/framework/.claude/stages/lc-storytelling.yaml +37 -0
  110. package/framework/.claude/stages/product-design-docs.yaml +66 -0
  111. package/framework/.claude/stages/product-prototype.yaml +53 -0
  112. package/framework/.claude/stages/propose.yaml +45 -0
  113. package/framework/.claude/stages/split.yaml +41 -0
  114. package/framework/.claude/stages/sync-knowledge.yaml +49 -0
  115. package/framework/.claude/stages/tdd-development-plan.yaml +43 -0
  116. package/framework/.claude/stages/workflow-router.yaml +45 -0
  117. package/framework/.claude/templates/solo-workflow-context.yaml +23 -0
  118. package/framework/.claude/workflows/README.md +93 -0
  119. package/framework/.claude/workflows/bug-fix.yaml +81 -0
  120. package/framework/.claude/workflows/design-spike.yaml +55 -0
  121. package/framework/.claude/workflows/existing-project-iteration.yaml +106 -0
  122. package/framework/.claude/workflows/interview-to-knowledge.yaml +189 -0
  123. package/framework/.claude/workflows/oss-first-design.yaml +100 -0
  124. package/framework/.claude/workflows/pm-research.yaml +115 -0
  125. package/framework/.claude-plugin/hooks.json +6 -0
  126. package/framework/.claude-plugin/marketplace.json +32 -0
  127. package/framework/.claude-plugin/plugin.json +57 -0
  128. package/framework/.version-bump.json +43 -0
  129. package/framework/CLAUDE.md +140 -0
  130. package/framework/CONTRIBUTING.md +219 -0
  131. package/framework/LICENSE +21 -0
  132. package/framework/README.en.md +139 -0
  133. package/framework/README.md +139 -0
  134. package/framework/RELEASE.md +334 -0
  135. package/framework/SKILL.md +24 -0
  136. package/framework/USAGE.md +356 -0
  137. package/framework/manifest.json +1214 -0
  138. package/framework/requirements.txt +4 -0
  139. package/framework/scripts/__init__.py +0 -0
  140. package/framework/scripts/build_codebase_knowledge.py +76 -0
  141. package/framework/scripts/bump_version.py +181 -0
  142. package/framework/scripts/check-dependency-drift.py +69 -0
  143. package/framework/scripts/check-harness-drift.sh +57 -0
  144. package/framework/scripts/check-main-merge-hygiene.py +128 -0
  145. package/framework/scripts/check-skill-dependencies.sh +124 -0
  146. package/framework/scripts/init-lincoln-branch.sh +300 -0
  147. package/framework/scripts/init-project.sh +184 -0
  148. package/framework/scripts/lc-benchmark-cli.py +66 -0
  149. package/framework/scripts/lincoln-audit.py +288 -0
  150. package/framework/scripts/lincoln-setup.py +651 -0
  151. package/framework/scripts/lincoln-status.py +448 -0
  152. package/framework/scripts/lincoln_benchmark.py +224 -0
  153. package/framework/scripts/lincoln_benchmark_eval.py +104 -0
  154. package/framework/scripts/lincoln_benchmark_metrics.py +818 -0
  155. package/framework/scripts/lincoln_benchmark_report.py +141 -0
  156. package/framework/scripts/lincoln_command_map.py +289 -0
  157. package/framework/scripts/lincoln_dependency_manager.py +335 -0
  158. package/framework/scripts/lincoln_documents.py +175 -0
  159. package/framework/scripts/lincoln_harness_adapter.py +319 -0
  160. package/framework/scripts/lincoln_index.py +255 -0
  161. package/framework/scripts/lincoln_paths.py +198 -0
  162. package/framework/scripts/lincoln_prd.py +227 -0
  163. package/framework/scripts/lincoln_role.py +46 -0
  164. package/framework/scripts/lincoln_scenario.py +59 -0
  165. package/framework/scripts/lincoln_session_start_metrics.py +71 -0
  166. package/framework/scripts/lincoln_skill_prompt.py +48 -0
  167. package/framework/scripts/lincoln_trace.py +335 -0
  168. package/framework/scripts/lincoln_workflow.py +199 -0
  169. package/framework/scripts/list-active-lincoln-branches.sh +87 -0
  170. package/framework/scripts/package-lincoln-plugin.py +340 -0
  171. package/framework/scripts/run-infrastructure-tests.py +84 -0
  172. package/framework/scripts/stage_loader.py +1071 -0
  173. package/framework/scripts/static-check.sh +356 -0
  174. package/framework/scripts/sync-external-agents.sh +210 -0
  175. package/framework/scripts/sync-framework-package.mjs +218 -0
  176. package/framework/scripts/task_tool_guard.py +213 -0
  177. package/framework/scripts/track-artifacts.py +140 -0
  178. package/framework/scripts/validate_stage.py +350 -0
  179. package/{README.md → framework/tools/lincoln/README.md} +4 -4
  180. package/framework/tools/lincoln/package-lock.json +3349 -0
  181. package/framework/tools/lincoln/package.json +48 -0
  182. package/framework/tools/lincoln/src/cli.tsx +60 -0
  183. package/framework/tools/lincoln/src/components/AudioMeter.tsx +34 -0
  184. package/framework/tools/lincoln/src/components/CancelledScreen.tsx +11 -0
  185. package/framework/tools/lincoln/src/components/DevicesMenu.tsx +79 -0
  186. package/framework/tools/lincoln/src/components/HintBar.tsx +25 -0
  187. package/framework/tools/lincoln/src/components/LogView.tsx +36 -0
  188. package/framework/tools/lincoln/src/components/ModelMenu.tsx +122 -0
  189. package/framework/tools/lincoln/src/components/ReadyScreen.tsx +54 -0
  190. package/framework/tools/lincoln/src/components/RecordingApp.tsx +211 -0
  191. package/framework/tools/lincoln/src/components/RecordingScreen.tsx +41 -0
  192. package/framework/tools/lincoln/src/components/SelectMenu.tsx +53 -0
  193. package/framework/tools/lincoln/src/components/StatusPanel.tsx +52 -0
  194. package/framework/tools/lincoln/src/components/StopConfirmation.tsx +32 -0
  195. package/framework/tools/lincoln/src/config/args.ts +35 -0
  196. package/framework/tools/lincoln/src/config/loadConfig.ts +81 -0
  197. package/framework/tools/lincoln/src/config/resolveConfig.ts +85 -0
  198. package/framework/tools/lincoln/src/devices/listDevices.ts +11 -0
  199. package/framework/tools/lincoln/src/devices/parseDevices.ts +25 -0
  200. package/framework/tools/lincoln/src/hooks/useKeyHandler.ts +63 -0
  201. package/framework/tools/lincoln/src/main.tsx +4 -0
  202. package/framework/tools/lincoln/src/models/models.ts +34 -0
  203. package/framework/tools/lincoln/src/models/runWarmup.ts +46 -0
  204. package/framework/tools/lincoln/src/models/warmupProgress.ts +30 -0
  205. package/framework/tools/lincoln/src/recording/spawnRecorder.ts +127 -0
  206. package/framework/tools/lincoln/src/recording/useRecorder.ts +146 -0
  207. package/framework/tools/lincoln/src/utils/amplitudeToMeter.ts +4 -0
  208. package/framework/tools/lincoln/src/utils/formatDuration.ts +6 -0
  209. package/framework/tools/lincoln/src/utils/sessionId.ts +25 -0
  210. package/framework/tools/lincoln/tests/cli.test.tsx +52 -0
  211. package/framework/tools/lincoln/tests/components/AudioMeter.test.tsx +31 -0
  212. package/framework/tools/lincoln/tests/components/CancelledScreen.test.tsx +12 -0
  213. package/framework/tools/lincoln/tests/components/ReadyScreen.test.tsx +68 -0
  214. package/framework/tools/lincoln/tests/components/RecordingApp.menus.test.tsx +158 -0
  215. package/framework/tools/lincoln/tests/components/RecordingApp.test.tsx +248 -0
  216. package/framework/tools/lincoln/tests/components/RecordingScreen.test.tsx +37 -0
  217. package/framework/tools/lincoln/tests/components/SelectMenu.test.tsx +59 -0
  218. package/framework/tools/lincoln/tests/components/StopConfirmation.test.tsx +38 -0
  219. package/framework/tools/lincoln/tests/config/args.test.ts +49 -0
  220. package/framework/tools/lincoln/tests/config/loadConfig.test.ts +59 -0
  221. package/framework/tools/lincoln/tests/config/resolveConfig.test.ts +72 -0
  222. package/framework/tools/lincoln/tests/devices/parseDevices.test.ts +21 -0
  223. package/framework/tools/lincoln/tests/hooks/useKeyHandler.enabled.test.tsx +44 -0
  224. package/framework/tools/lincoln/tests/hooks/useKeyHandler.test.tsx +130 -0
  225. package/framework/tools/lincoln/tests/models/models.test.ts +27 -0
  226. package/framework/tools/lincoln/tests/models/warmupProgress.test.ts +25 -0
  227. package/framework/tools/lincoln/tests/recording/spawnRecorder.test.ts +85 -0
  228. package/framework/tools/lincoln/tests/recording/useRecorder.test.tsx +172 -0
  229. package/framework/tools/lincoln/tests/utils/amplitudeToMeter.test.ts +22 -0
  230. package/framework/tools/lincoln/tests/utils/formatDuration.test.ts +11 -0
  231. package/framework/tools/lincoln/tests/utils/sessionId.test.ts +30 -0
  232. package/framework/tools/lincoln/tsconfig.json +22 -0
  233. package/framework/tools/lincoln/tsconfig.tests.json +10 -0
  234. package/framework/tools/lincoln/tsup.config.ts +17 -0
  235. package/framework/tools/lincoln/vitest.config.ts +20 -0
  236. package/framework/tools/lincoln-installer/package-lock.json +3611 -0
  237. package/framework/tools/lincoln-installer/package.json +53 -0
  238. package/framework/tools/lincoln-installer/src/cli.tsx +63 -0
  239. package/framework/tools/lincoln-installer/src/components/HarnessSelectScreen.tsx +43 -0
  240. package/framework/tools/lincoln-installer/src/components/InstallerApp.tsx +98 -0
  241. package/framework/tools/lincoln-installer/src/components/OptionsScreen.tsx +56 -0
  242. package/framework/tools/lincoln-installer/src/components/ProgressScreen.tsx +25 -0
  243. package/framework/tools/lincoln-installer/src/components/ResultScreen.tsx +40 -0
  244. package/framework/tools/lincoln-installer/src/components/SelectMenu.tsx +73 -0
  245. package/framework/tools/lincoln-installer/src/components/SummaryScreen.tsx +49 -0
  246. package/framework/tools/lincoln-installer/src/components/UpdateApp.tsx +135 -0
  247. package/framework/tools/lincoln-installer/src/components/WelcomeScreen.tsx +22 -0
  248. package/framework/tools/lincoln-installer/src/config/args.ts +76 -0
  249. package/framework/tools/lincoln-installer/src/config/resolveConfig.ts +33 -0
  250. package/framework/tools/lincoln-installer/src/config/updateArgs.ts +67 -0
  251. package/framework/tools/lincoln-installer/src/hooks/useSetup.ts +149 -0
  252. package/framework/tools/lincoln-installer/src/main.tsx +11 -0
  253. package/framework/tools/lincoln-installer/src/types.ts +19 -0
  254. package/framework/tools/lincoln-installer/src/update-main.tsx +11 -0
  255. package/framework/tools/lincoln-installer/src/update.tsx +90 -0
  256. package/framework/tools/lincoln-installer/src/utils/detectHarness.ts +51 -0
  257. package/framework/tools/lincoln-installer/src/utils/runSetup.ts +62 -0
  258. package/framework/tools/lincoln-installer/src/utils/updateRelease.ts +454 -0
  259. package/framework/tools/lincoln-installer/tests/components/InstallerApp.test.tsx +88 -0
  260. package/framework/tools/lincoln-installer/tests/components/SelectMenu.test.tsx +26 -0
  261. package/framework/tools/lincoln-installer/tests/components/UpdateApp.test.tsx +170 -0
  262. package/framework/tools/lincoln-installer/tests/components/screens.test.tsx +310 -0
  263. package/framework/tools/lincoln-installer/tests/config/args.test.ts +66 -0
  264. package/framework/tools/lincoln-installer/tests/config/resolveConfig.test.ts +75 -0
  265. package/framework/tools/lincoln-installer/tests/config/updateArgs.test.ts +52 -0
  266. package/framework/tools/lincoln-installer/tests/hooks/useSetup.test.tsx +213 -0
  267. package/framework/tools/lincoln-installer/tests/update.test.tsx +108 -0
  268. package/framework/tools/lincoln-installer/tests/utils/detectHarness.test.ts +40 -0
  269. package/framework/tools/lincoln-installer/tests/utils/runSetup.test.ts +45 -0
  270. package/framework/tools/lincoln-installer/tests/utils/updateRelease.test.ts +559 -0
  271. package/framework/tools/lincoln-installer/tsconfig.json +22 -0
  272. package/framework/tools/lincoln-installer/tsconfig.tests.json +8 -0
  273. package/framework/tools/lincoln-installer/tsup.config.ts +17 -0
  274. package/framework/tools/lincoln-installer/vitest.config.ts +20 -0
  275. package/framework/tools/lincoln-record/Cargo.lock +1896 -0
  276. package/framework/tools/lincoln-record/Cargo.toml +24 -0
  277. package/framework/tools/lincoln-record/diarize/diarize.py +117 -0
  278. package/framework/tools/lincoln-record/diarize/merge.py +55 -0
  279. package/framework/tools/lincoln-record/src/audio/capture/microphone.rs +194 -0
  280. package/framework/tools/lincoln-record/src/audio/capture/mod.rs +1 -0
  281. package/framework/tools/lincoln-record/src/audio/devices.rs +20 -0
  282. package/framework/tools/lincoln-record/src/audio/mod.rs +5 -0
  283. package/framework/tools/lincoln-record/src/audio/resampler.rs +44 -0
  284. package/framework/tools/lincoln-record/src/audio/saver.rs +87 -0
  285. package/framework/tools/lincoln-record/src/audio/source.rs +104 -0
  286. package/framework/tools/lincoln-record/src/cli.rs +96 -0
  287. package/framework/tools/lincoln-record/src/config.rs +135 -0
  288. package/framework/tools/lincoln-record/src/lib.rs +6 -0
  289. package/framework/tools/lincoln-record/src/main.rs +242 -0
  290. package/framework/tools/lincoln-record/src/model/mod.rs +248 -0
  291. package/framework/tools/lincoln-record/src/output/metadata.rs +30 -0
  292. package/framework/tools/lincoln-record/src/output/mod.rs +2 -0
  293. package/framework/tools/lincoln-record/src/output/transcript.rs +42 -0
  294. package/framework/tools/lincoln-record/src/transcription/diarization.rs +64 -0
  295. package/framework/tools/lincoln-record/src/transcription/mod.rs +3 -0
  296. package/framework/tools/lincoln-record/src/transcription/provider.rs +43 -0
  297. package/framework/tools/lincoln-record/src/transcription/whisper.rs +176 -0
  298. package/framework/tools/lincoln-record/tests/cli_args.rs +103 -0
  299. package/framework/tools/lincoln-record/tests/config_test.rs +70 -0
  300. package/framework/tools/lincoln-record/tests/devices_test.rs +23 -0
  301. package/framework/tools/lincoln-record/tests/diarization_tests.rs +101 -0
  302. package/framework/tools/lincoln-record/tests/integration_tests.rs +44 -0
  303. package/framework/tools/lincoln-record/tests/model_test.rs +68 -0
  304. package/framework/tools/lincoln-record/tests/transcription_tests.rs +73 -0
  305. package/package.json +19 -13
@@ -0,0 +1,454 @@
1
+ import { createWriteStream } from 'node:fs'
2
+ import {
3
+ copyFileSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ readFileSync,
8
+ rmSync,
9
+ } from 'node:fs'
10
+ import { dirname, join, relative, resolve, sep } from 'node:path'
11
+ import { extract, ReadEntry } from 'tar'
12
+
13
+ export interface ReleaseInfo {
14
+ version: string
15
+ assetUrl: string
16
+ publishedAt: string
17
+ checksumUrl?: string
18
+ }
19
+
20
+ export interface MergeResult {
21
+ updated: string[]
22
+ preserved: string[]
23
+ backupDir: string
24
+ }
25
+
26
+ export interface UpdateOptions {
27
+ root: string
28
+ repo?: string
29
+ dryRun?: boolean
30
+ }
31
+
32
+ export interface UpdateResult {
33
+ success: boolean
34
+ fromVersion?: string
35
+ toVersion?: string
36
+ updated: string[]
37
+ preserved: string[]
38
+ backupDir?: string
39
+ error?: string
40
+ }
41
+
42
+ const GITHUB_API_BASE = 'https://api.github.com/repos'
43
+ const DEFAULT_REPO = 'sushanglewis/Lincoln'
44
+ const FETCH_TIMEOUT_MS = 15_000
45
+ const DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024 // 100 MB
46
+ const ALLOWED_ASSET_HOSTS = new Set([
47
+ 'github.com',
48
+ 'objects.githubusercontent.com',
49
+ 'release-assets.githubusercontent.com',
50
+ ])
51
+ const REPO_SLUG_REGEX = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/
52
+
53
+ export const ALLOWLIST_DIRS = ['.claude', '.claude-plugin', 'scripts', 'tools']
54
+ export const ALLOWLIST_FILES = [
55
+ 'README.md',
56
+ 'CLAUDE.md',
57
+ 'LICENSE',
58
+ 'RELEASE.md',
59
+ '.version-bump.json',
60
+ 'requirements.txt',
61
+ 'SKILL.md',
62
+ ]
63
+
64
+ export const PRESERVE_PATHS = [
65
+ '.context',
66
+ '.github/openspec-config.yml',
67
+ 'recordings',
68
+ 'issue-',
69
+ '.venv',
70
+ 'venv',
71
+ ]
72
+
73
+ export function validateRepoSlug(repo: string): void {
74
+ if (!REPO_SLUG_REGEX.test(repo)) {
75
+ throw new Error(`Invalid repository slug: ${repo}`)
76
+ }
77
+ }
78
+
79
+ export function getCurrentVersion(root: string): string | undefined {
80
+ const path = join(root, '.version-bump.json')
81
+ if (!existsSync(path)) {
82
+ return undefined
83
+ }
84
+ try {
85
+ const data = JSON.parse(readFileSync(path, 'utf-8'))
86
+ if (typeof data.version === 'string' && data.version) {
87
+ return data.version
88
+ }
89
+ } catch {
90
+ return undefined
91
+ }
92
+ return undefined
93
+ }
94
+
95
+ function parseVersion(version: string): number[] {
96
+ return version.split('.').map((part) => {
97
+ const num = Number(part)
98
+ return Number.isNaN(num) ? 0 : num
99
+ })
100
+ }
101
+
102
+ export function isUpdateNeeded(current: string, latest: string): boolean {
103
+ const currentParts = parseVersion(current)
104
+ const latestParts = parseVersion(latest)
105
+ const maxLength = Math.max(currentParts.length, latestParts.length)
106
+ for (let i = 0; i < maxLength; i++) {
107
+ const currentPart = currentParts[i] ?? 0
108
+ const latestPart = latestParts[i] ?? 0
109
+ if (latestPart > currentPart) {
110
+ return true
111
+ }
112
+ if (latestPart < currentPart) {
113
+ return false
114
+ }
115
+ }
116
+ return false
117
+ }
118
+
119
+ function isSemverTag(tag: string): boolean {
120
+ return /^v?\d+(\.\d+)*$/.test(tag)
121
+ }
122
+
123
+ export async function fetchLatestRelease(repo: string): Promise<ReleaseInfo> {
124
+ validateRepoSlug(repo)
125
+ const url = `${GITHUB_API_BASE}/${repo}/releases/latest`
126
+ const response = await fetch(url, {
127
+ headers: { Accept: 'application/vnd.github+json' },
128
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
129
+ })
130
+ if (!response.ok) {
131
+ throw new Error(`GitHub API error ${response.status}: ${response.statusText}`)
132
+ }
133
+ const data = (await response.json()) as {
134
+ tag_name: string
135
+ published_at: string
136
+ assets: Array<{ name: string; browser_download_url: string }>
137
+ }
138
+ const version = data.tag_name.replace(/^v/, '')
139
+ if (!isSemverTag(data.tag_name)) {
140
+ throw new Error(`Unexpected release tag: ${data.tag_name}`)
141
+ }
142
+ const asset = data.assets.find((a) => a.name.endsWith('.tar.gz'))
143
+ if (!asset) {
144
+ throw new Error(`No tar.gz asset found for release ${data.tag_name}`)
145
+ }
146
+ const checksumAsset = data.assets.find((a) => a.name === `${asset.name}.sha256`)
147
+ return {
148
+ version,
149
+ assetUrl: asset.browser_download_url,
150
+ publishedAt: data.published_at,
151
+ checksumUrl: checksumAsset?.browser_download_url,
152
+ }
153
+ }
154
+
155
+ function validateAssetUrl(url: string): void {
156
+ const parsed = new URL(url)
157
+ if (parsed.protocol !== 'https:') {
158
+ throw new Error(`Download URL must use HTTPS: ${url}`)
159
+ }
160
+ if (!ALLOWED_ASSET_HOSTS.has(parsed.hostname)) {
161
+ throw new Error(`Unexpected download host: ${parsed.hostname}`)
162
+ }
163
+ }
164
+
165
+ async function fetchAsset(url: string, redirectCount = 0): Promise<Response> {
166
+ if (redirectCount > 5) {
167
+ throw new Error('Too many download redirects')
168
+ }
169
+ validateAssetUrl(url)
170
+ const response = await fetch(url, {
171
+ redirect: 'manual',
172
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
173
+ })
174
+ if (response.status >= 300 && response.status < 400) {
175
+ const location = response.headers.get('location')
176
+ if (!location) {
177
+ throw new Error(`Redirect without Location header from ${url}`)
178
+ }
179
+ return fetchAsset(new URL(location, url).toString(), redirectCount + 1)
180
+ }
181
+ return response
182
+ }
183
+
184
+ async function writeBodyWithLimit(
185
+ body: ReadableStream<Uint8Array> | null,
186
+ dest: string,
187
+ maxBytes: number
188
+ ): Promise<void> {
189
+ if (!body) {
190
+ throw new Error('Download response has no body')
191
+ }
192
+ mkdirSync(dirname(dest), { recursive: true })
193
+ const reader = body.getReader()
194
+ const fileStream = createWriteStream(dest)
195
+ let received = 0
196
+
197
+ try {
198
+ while (true) {
199
+ const { done, value } = await reader.read()
200
+ if (done) break
201
+ received += value.byteLength
202
+ if (received > maxBytes) {
203
+ throw new Error(`Download exceeds maximum size of ${maxBytes} bytes`)
204
+ }
205
+ fileStream.write(value)
206
+ }
207
+ } finally {
208
+ fileStream.end()
209
+ }
210
+
211
+ await new Promise<void>((resolve, reject) => {
212
+ fileStream.on('finish', resolve)
213
+ fileStream.on('error', reject)
214
+ })
215
+ }
216
+
217
+ export async function downloadAsset(url: string, dest: string): Promise<void> {
218
+ const response = await fetchAsset(url)
219
+ if (!response.ok) {
220
+ throw new Error(`Download failed ${response.status}: ${response.statusText}`)
221
+ }
222
+ await writeBodyWithLimit(response.body, dest, DOWNLOAD_MAX_BYTES)
223
+ }
224
+
225
+ async function verifyChecksum(archivePath: string, checksumUrl: string): Promise<void> {
226
+ const response = await fetchAsset(checksumUrl)
227
+ if (!response.ok) {
228
+ throw new Error(`Checksum download failed ${response.status}: ${response.statusText}`)
229
+ }
230
+ const text = await response.text()
231
+ const expected = text.trim().split(/\s+/)[0]?.toLowerCase()
232
+ if (!expected) {
233
+ throw new Error('Checksum file is empty')
234
+ }
235
+ const actual = await computeSha256(archivePath)
236
+ if (actual !== expected) {
237
+ throw new Error(`Checksum mismatch: expected ${expected}, got ${actual}`)
238
+ }
239
+ }
240
+
241
+ async function computeSha256(filePath: string): Promise<string> {
242
+ const { createHash } = await import('node:crypto')
243
+ const hash = createHash('sha256')
244
+ const { readFile } = await import('node:fs/promises')
245
+ hash.update(await readFile(filePath))
246
+ return hash.digest('hex').toLowerCase()
247
+ }
248
+
249
+ export async function extractArchive(archivePath: string, dest: string): Promise<void> {
250
+ mkdirSync(dest, { recursive: true })
251
+ await extract({
252
+ file: archivePath,
253
+ cwd: dest,
254
+ filter: (_path: string, entry: ReadEntry | import('node:fs').Stats) => {
255
+ const readEntry = entry as ReadEntry
256
+ return readEntry.type === 'File' || readEntry.type === 'Directory'
257
+ },
258
+ })
259
+ }
260
+
261
+ function normalizeRelativePath(relativePath: string): string {
262
+ return relativePath.replace(/\\/g, '/')
263
+ }
264
+
265
+ export function isPreserved(relativePath: string): boolean {
266
+ const posix = normalizeRelativePath(relativePath)
267
+ return PRESERVE_PATHS.some((prefix) => {
268
+ if (posix === prefix) return true
269
+ if (prefix.endsWith('-')) {
270
+ return posix.startsWith(prefix)
271
+ }
272
+ return posix.startsWith(`${prefix}/`)
273
+ })
274
+ }
275
+
276
+ function backupPath(projectRoot: string, relativePath: string): string {
277
+ return resolve(projectRoot, '.context', 'lincoln-update-backup', relativePath)
278
+ }
279
+
280
+ function backupIfExists(projectRoot: string, relativePath: string): void {
281
+ const target = resolve(projectRoot, relativePath)
282
+ if (!existsSync(target)) {
283
+ return
284
+ }
285
+ const backup = backupPath(projectRoot, relativePath)
286
+ mkdirSync(dirname(backup), { recursive: true })
287
+ copyFileSync(target, backup)
288
+ }
289
+
290
+ function restoreBackups(projectRoot: string, relativePaths: string[]): void {
291
+ for (const relativePath of relativePaths) {
292
+ const backup = backupPath(projectRoot, relativePath)
293
+ const target = resolve(projectRoot, relativePath)
294
+ if (existsSync(backup)) {
295
+ mkdirSync(dirname(target), { recursive: true })
296
+ copyFileSync(backup, target)
297
+ }
298
+ }
299
+ }
300
+
301
+ export function resolveStagingRoot(stagingRoot: string): string {
302
+ const entries = readdirSync(stagingRoot, { withFileTypes: true })
303
+ if (entries.length === 1 && entries[0].isDirectory()) {
304
+ return resolve(stagingRoot, entries[0].name)
305
+ }
306
+ return stagingRoot
307
+ }
308
+
309
+ export async function mergeRelease(
310
+ stagingRoot: string,
311
+ projectRoot: string
312
+ ): Promise<MergeResult> {
313
+ const updated: string[] = []
314
+ const backedUp: string[] = []
315
+ const preservedSet = new Set<string>()
316
+ const resolvedStaging = resolveStagingRoot(stagingRoot)
317
+ const backupDir = resolve(projectRoot, '.context', 'lincoln-update-backup')
318
+
319
+ function collectFiles(dir: string): string[] {
320
+ const files: string[] = []
321
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
322
+ if (entry.isSymbolicLink()) {
323
+ continue
324
+ }
325
+ const fullPath = join(dir, entry.name)
326
+ if (entry.isDirectory()) {
327
+ files.push(...collectFiles(fullPath))
328
+ } else if (entry.isFile()) {
329
+ files.push(fullPath)
330
+ }
331
+ }
332
+ return files
333
+ }
334
+
335
+ function processSource(sourcePath: string, relativePath: string): void {
336
+ if (isPreserved(relativePath)) {
337
+ const existingTarget = resolve(projectRoot, relativePath)
338
+ if (existsSync(existingTarget)) {
339
+ const topLevel = PRESERVE_PATHS.find((prefix) => {
340
+ const posix = normalizeRelativePath(relativePath)
341
+ if (prefix.endsWith('-')) {
342
+ return posix === prefix || posix.startsWith(prefix)
343
+ }
344
+ return posix === prefix || posix.startsWith(`${prefix}/`)
345
+ })
346
+ if (topLevel) {
347
+ preservedSet.add(topLevel)
348
+ }
349
+ }
350
+ return
351
+ }
352
+
353
+ const targetPath = resolve(projectRoot, relativePath)
354
+ backupIfExists(projectRoot, relativePath)
355
+ backedUp.push(relativePath)
356
+ mkdirSync(dirname(targetPath), { recursive: true })
357
+ copyFileSync(sourcePath, targetPath)
358
+ updated.push(relativePath)
359
+ }
360
+
361
+ try {
362
+ for (const dirName of ALLOWLIST_DIRS) {
363
+ const sourceDir = join(resolvedStaging, dirName)
364
+ if (!existsSync(sourceDir)) {
365
+ continue
366
+ }
367
+ for (const file of collectFiles(sourceDir)) {
368
+ const rel = relative(sourceDir, file)
369
+ processSource(file, normalizeRelativePath(join(dirName, rel)))
370
+ }
371
+ }
372
+
373
+ for (const fileName of ALLOWLIST_FILES) {
374
+ const sourceFile = join(resolvedStaging, fileName)
375
+ if (!existsSync(sourceFile)) {
376
+ continue
377
+ }
378
+ processSource(sourceFile, fileName)
379
+ }
380
+
381
+ for (const prefix of PRESERVE_PATHS) {
382
+ if (existsSync(resolve(projectRoot, prefix))) {
383
+ preservedSet.add(prefix)
384
+ }
385
+ }
386
+ } catch (err) {
387
+ restoreBackups(projectRoot, backedUp)
388
+ throw err
389
+ }
390
+
391
+ return { updated, preserved: Array.from(preservedSet), backupDir }
392
+ }
393
+
394
+ export async function updateRelease(options: UpdateOptions): Promise<UpdateResult> {
395
+ const { root, repo = DEFAULT_REPO, dryRun = false } = options
396
+
397
+ try {
398
+ const currentVersion = getCurrentVersion(root)
399
+ if (!currentVersion) {
400
+ return { success: false, updated: [], preserved: [], error: 'Could not detect current Lincoln version' }
401
+ }
402
+
403
+ const latest = await fetchLatestRelease(repo)
404
+ if (!isUpdateNeeded(currentVersion, latest.version)) {
405
+ return {
406
+ success: true,
407
+ fromVersion: currentVersion,
408
+ toVersion: currentVersion,
409
+ updated: [],
410
+ preserved: [],
411
+ }
412
+ }
413
+
414
+ if (dryRun) {
415
+ return {
416
+ success: true,
417
+ fromVersion: currentVersion,
418
+ toVersion: latest.version,
419
+ updated: [],
420
+ preserved: [],
421
+ }
422
+ }
423
+
424
+ const tempDir = join(root, '.context', 'lincoln-update-staging')
425
+ mkdirSync(tempDir, { recursive: true })
426
+
427
+ try {
428
+ const archivePath = join(tempDir, 'lincoln-latest.tar.gz')
429
+ const extractDir = join(tempDir, 'extracted')
430
+
431
+ await downloadAsset(latest.assetUrl, archivePath)
432
+ if (latest.checksumUrl) {
433
+ await verifyChecksum(archivePath, latest.checksumUrl)
434
+ }
435
+ await extractArchive(archivePath, extractDir)
436
+
437
+ const merge = await mergeRelease(extractDir, root)
438
+
439
+ return {
440
+ success: true,
441
+ fromVersion: currentVersion,
442
+ toVersion: latest.version,
443
+ updated: merge.updated,
444
+ preserved: merge.preserved,
445
+ backupDir: merge.backupDir,
446
+ }
447
+ } finally {
448
+ rmSync(tempDir, { recursive: true, force: true })
449
+ }
450
+ } catch (err) {
451
+ const message = err instanceof Error ? err.message : String(err)
452
+ return { success: false, updated: [], preserved: [], error: message }
453
+ }
454
+ }
@@ -0,0 +1,88 @@
1
+ import React from 'react'
2
+ import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'
3
+ import { render } from 'ink-testing-library'
4
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
5
+ import { tmpdir } from 'node:os'
6
+ import { join } from 'node:path'
7
+ import { InstallerApp } from '../../src/components/InstallerApp'
8
+ import type { InstallerConfig } from '../../src/config/resolveConfig'
9
+ import * as runSetupModule from '../../src/utils/runSetup'
10
+
11
+ async function tick() {
12
+ return new Promise((resolve) => setTimeout(resolve, 0))
13
+ }
14
+
15
+ function createProjectDir(): string {
16
+ const root = mkdtempSync(join(tmpdir(), 'lincoln-'))
17
+ const scriptsDir = join(root, 'scripts')
18
+ mkdirSync(scriptsDir)
19
+ writeFileSync(join(scriptsDir, 'lincoln-setup.py'), '')
20
+ return root
21
+ }
22
+
23
+ describe('InstallerApp', () => {
24
+ const dirs: string[] = []
25
+
26
+ beforeEach(() => {
27
+ vi.spyOn(runSetupModule, 'runSetup').mockResolvedValue({
28
+ success: true,
29
+ code: 0,
30
+ stdout: '{"ok":true}',
31
+ stderr: '',
32
+ })
33
+ })
34
+
35
+ afterEach(() => {
36
+ vi.restoreAllMocks()
37
+ for (const dir of dirs) {
38
+ rmSync(dir, { recursive: true, force: true })
39
+ }
40
+ dirs.length = 0
41
+ })
42
+
43
+ test('renders welcome screen by default', () => {
44
+ const root = createProjectDir()
45
+ dirs.push(root)
46
+ const config: InstallerConfig = {
47
+ root,
48
+ harnesses: [],
49
+ yes: false,
50
+ dryRun: false,
51
+ format: 'human',
52
+ }
53
+ const { lastFrame } = render(<InstallerApp config={config} />)
54
+ expect(lastFrame() ?? '').toContain('Welcome to Lincoln')
55
+ })
56
+
57
+ test('navigates from welcome to summary via input', async () => {
58
+ const root = createProjectDir()
59
+ dirs.push(root)
60
+ const config: InstallerConfig = {
61
+ root,
62
+ harnesses: [],
63
+ yes: false,
64
+ dryRun: false,
65
+ format: 'human',
66
+ }
67
+ const { lastFrame, stdin } = render(<InstallerApp config={config} />)
68
+
69
+ await tick()
70
+ stdin.write('\r')
71
+ await tick()
72
+ expect(lastFrame() ?? '').toContain('Select agent harnesses')
73
+
74
+ stdin.write('\r')
75
+ await tick()
76
+ expect(lastFrame() ?? '').toContain('Options')
77
+
78
+ stdin.write('\r')
79
+ await tick()
80
+ expect(lastFrame() ?? '').toContain('Summary')
81
+
82
+ stdin.write('\r')
83
+ await tick()
84
+ await vi.waitFor(() => expect(lastFrame() ?? '').toContain('complete'), {
85
+ timeout: 2000,
86
+ })
87
+ })
88
+ })
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+ import { describe, expect, test, vi } from 'vitest'
3
+ import { render } from 'ink-testing-library'
4
+ import { SelectMenu } from '../../src/components/SelectMenu'
5
+
6
+ describe('SelectMenu', () => {
7
+ test('renders items and marks selected', () => {
8
+ const onSelect = vi.fn()
9
+ const onConfirm = vi.fn()
10
+ const { lastFrame } = render(
11
+ <SelectMenu
12
+ title="Choose"
13
+ items={[
14
+ { label: 'One', value: 'one' },
15
+ { label: 'Two', value: 'two' },
16
+ ]}
17
+ selected={['one']}
18
+ onSelect={onSelect}
19
+ onConfirm={onConfirm}
20
+ />
21
+ )
22
+ const frame = lastFrame() ?? ''
23
+ expect(frame).toContain('One')
24
+ expect(frame).toContain('Two')
25
+ })
26
+ })